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:
- Each call with
build: falsestages 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. validate_only: truechecks the whole draft.- One call without
buildbuilds 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:
- Pack the source:
tar czf app.tar.gz -C <dir> .(.tarand.zipwork too; a single top-level folder is stripped, and file modes are kept). sha256sum app.tar.gz.lenshub_app_upload_urlwith thatsha256, andname(a new app) orslug(a redeploy).- Run the
curlthe 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.