LensHub/ docs / deploying
Apps

Deploying

How an agent ships an app, and what happens to it.

From the agent

lenshub_app_deploy takes the app's source as a map of file path to content (binary files, such as images and fonts, go in files_base64). The first call creates the app and returns its slug; calling again with that slug builds a new release rather than a second app.

The build can install dependencies: it reaches public hosts — PyPI, npm, OS package mirrors, release downloads — through LensHub's egress proxy, and never an internal address. Every host a build contacts is recorded on the app as a build_egress event. If your builds install from a private registry, an operator lists it once in APPD_BUILD_ALLOW.

What arrived

Every deploy answers with a report: the file count, bytes and source hash, how the source arrived, and what changed since the previous release (added, removed, changed). A missing or damaged file shows up there, before the build does. lenshub_app_files lists the files of any release, or reads one back.

A deploy replaces the whole source unless it says otherwise. To change a few files of an existing app, deploy with merge: true and only those files; delete removes paths. A full deploy that drops files from the previous release carries a warning pointing at merge.

Check before building

validate_only: true runs every check a deploy does — the paths and any merge, the template or Dockerfile and its base images, each connection in lenshub-app.yaml — in about a second, and builds nothing. It is the cheap way to find a mistake that would otherwise cost a build.

Apps too big for one call

An agent that writes the app in its replies (claude.ai, Claude Desktop) has no shell to upload from, and one reply only holds so much. It sends the app over several calls instead:

  1. Each call with build: false stages files in the app's draft and builds nothing. The first call creates the app; later calls reuse its slug. Each answer says what the draft now holds and what that call changed.
  2. validate_only: true checks the whole draft.
  3. One call without build builds the draft, with that call's files on top.

A draft on an existing app starts from its latest release, so a later fix stages only what changes; reset_draft: true starts it empty. lenshub_app_status shows a draft that has not been built. A draft nobody has staged into for 7 days is removed. An archive upload or a REST deploy sends the whole source, so it discards a staged draft (and says so).

Large sources: upload from a shell

An agent that can run a shell uploads an archive instead of spelling the source out:

  1. Pack the source: tar czf app.tar.gz -C <dir> . (.tar and .zip work too; a single top-level folder is stripped, and file modes are kept).
  2. sha256sum app.tar.gz.
  3. lenshub_app_upload_url with that sha256, and name (a new app) or slug (a redeploy).
  4. Run the curl the tool returns. It PUTs the archive to the upload URL with the token in a header, Authorization: Upload <token> — never in the URL, so no access log along the way records it.

The upload deploys exactly like lenshub_app_deploy and answers with the same report. The link accepts only the archive whose sha256 it was made for, works once and expires after 15 minutes. It stops working if its person is demoted to viewer or the API key it came from is revoked, and handing it out is recorded in the app's history. A request without a live token is refused before its body is read, and only a few uploads are taken at a time (others get 429 Retry-After). An upload refused before anything was built — a damaged archive, a busy build slot — can be retried on the same link. A redeploy keeps the app's environment variables unless new ones are given.

Scripts that hold a write-scope API key can skip the link and send the archive straight to POST /api/apps/{slug}/deploy (or POST /api/apps?name=… for a new app) with Authorization: Bearer <key>.

After the build

Read lenshub_app_status. It returns:

  • the link the app is served at
  • the build state of the current release, and for a failed build the Dockerfile step that failed, the error line and the end of the log
  • the container state
  • the network map, with each connection's approval status

lenshub_app_logs has the whole build log alongside recent output. Read it rather than redeploying blind.

Releases

Every deploy is a release, retained with its build state, events and file list. The source of the newest five releases (and the running one) is kept, so merges and lenshub_app_files work; operators choose where with APPS_SOURCE_DIR. Rolling back selects an earlier release rather than rebuilding from older source.

Deleting an app stops its container and removes its volume, links and stored sources, but releases and events stay in the audit history — you keep the record of what ran.

On this page