Skip to content

Deploy status and recovery

Every deploy has an id (d_…). Any deploy prints it with -v (npx tori.host deploy -v, static site or service). Without it, npx tori.host deploy status with no id is the latest attempt for the app in ./tori.json. The console deploys tab lists the same ids.

Terminal window
npx tori.host deploy status
npx tori.host deploy status d_example
npx tori.host deploy watch d_example

See tori deploy for flags. In the console, open the app → deploys.

Every command shows what it is doing and what to do next, and nothing else. On a terminal one line at the bottom carries the current phase, its elapsed time and the server’s latest message; permanent lines mark the phases themselves. Piped or redirected — CI logs — there is no live line: phases and the server’s messages are printed as they happen, and a long wait is re-stated every 30 seconds so silence never looks like a hang.

-v / --verbose adds what is diagnostic rather than actionable: deploy and build identifiers, image references in full, every progress message with the time it arrived, and the phase timings. It works on every command. TORI_VERBOSE=1 is the same switch for CI, and NO_COLOR (any non-empty value) turns colour off, even when FORCE_COLOR is set; colour is used only where the terminal supports it, and never as the only carrier of meaning.

The timing line is one deploy’s progress, each phase given as the time from when the platform accepted the deploy:

timing zone 50ms · scheduled 300ms · pulled 400ms · container 900ms · revision 1.00s · programmed 1.00s · agent 2.80s · API 2.85s · client-wait 2.90s
Phase Reached when
zone The zone was told what to run
scheduled A node accepted the pod
pull-start The image fetch began. Shown only on a deploy that had to fetch its image
pulled The image is on the node — fetched, or already there
container Your process started
revision The new revision reported itself ready
programmed The gateway accepted the route onto the new revision
agent The zone checked that the gateway serves the new revision, and reported it
API The platform recorded the deploy as live
client-wait What this command waited, end to end

programmed and agent are two measurements of the same step and not one: the first is the gateway’s own record of accepting the route, the second is when the zone had checked that every gateway proxy answers for the new revision over HTTPS, and reported it. The gap between them is usually how long the zone took to look. A long gap can also mean the proxies took that long to start serving the new revision; until they did, requests could still reach the previous version (or, on a first deploy, no app). Treat the gap as diagnostic rather than as a number to act on.

Two things to know before reading it closely. programmed is when the gateway accepted the route configuration; pushing it out to the proxies is a further step that this timestamp does not cover, so it is not the instant the first request can arrive. And it is recorded only to the second, always rounded down — so the gap to agent reads as up to a second longer than it was, and a genuinely small gap still shows as several hundred milliseconds. Read these two across several deploys, not from one.

programmed is absent on a static deploy, on a zone that has not been updated to report it, and on an individual deploy where the zone could not see the route change happen — the gateway had not yet published its status when the zone reported, or the zone was restarting at that moment. The zone leaves it out rather than substitute a later change to the same route. That case is occasional — one missing programmed does not mean anything is wrong with the deploy or the zone.

pull-start is what tells a fetch from a cached image. A deploy whose image was already on the node shows pulled and no pull-start; one that fetched shows both, and the gap between them is roughly what the fetch cost.

Both come from the node’s own announcements, which Kubernetes delivers on a best-effort basis, so either can occasionally be missing. A missing phase is a gap in the record and not a statement about the deploy: pull-start without pulled is not a fetch that never finished — the deploy would not be live if it had not.

tori deploy status (no id) is the latest attempt for the app in ./tori.json. It prints the id, status, image (services, pinned to its digest in full), when it started, and — while it is building — the latest progress line.

The block is written to stdout and the suggested next commands to stderr, so npx tori.host deploy status 2>/dev/null keeps the answer and drops the commentary. tori whoami and tori --help are the same way; tori logs, tori env list and tori db status already were.

Typical statuses:

Status Meaning
building Accepted; not live yet (upload unpacking, or the service revision starting)
live This attempt is what the URL serves
failed Stopped. error is the reason
cancelled A pending service attempt was cancelled; the previous live version remains
superseded This attempt went live and a newer one has already replaced it

already live on a service deploy means that image and environment are already the running revision; nothing new was started unless you passed --restart.

tori deploy and tori deploy watch wait until the attempt settles. Ctrl+C stops watching; the deploy continues on the server. The CLI prints the watch command (and cancel for a pending service).

If the CLI gives up after its wait (twenty minutes, matching the server), it says so. The deploy has not been cancelled. Check the URL, deploy status, or the console.

Terminal window
npx tori.host deploy cancel d_example

tori deploy cancel <id> cancels a queued build at once. A build that is already running is marked for cancellation and the CLI waits for the builder to stop it, up to two minutes; if it has not stopped by then the CLI exits non-zero saying the deploy is still building, and the request stays recorded. Whatever was live keeps serving either way.

Cancellation applies only to pending service attempts. A queued source build, or an image deploy with no build, is cancelled immediately: the CLI exits 0 and another deploy can start. A leased or running source build is marked for cancellation and stays building until the builder acknowledges; the API answers 200 with that still-building row. The CLI waits for the builder, as above, and if it exits non-zero the request is still recorded: run deploy status or deploy watch until the attempt is cancelled. deploy watch exits 0 when the attempt ends cancelled, whether it was cancelled before the watch began or while it ran. Another deploy is refused with HTTP 409 until then. Resource cleanup follows in the zone. An existing live version remains available. A deploy that has already gone live cannot be cancelled. Repeating cancellation is harmless.

Static uploads that have already finished cannot be cancelled this way; wait for live or failed.

The customer’s copy of what went wrong is deploys.error. The CLI prints it as deploy failed: …. Common shapes:

Reason Recovery
Artefact too large, or a member file too large Shrink the output; deploy again
Unsafe path in the archive Stop packing .. or absolute names
Image pull / runAsNonRoot Fix the image; deploy the new digest
Nothing listening on 8080 Make the process listen on 8080, or deploy again with --port naming the port it does listen on (Another port)
Another deploy in progress Watch or cancel that id; a running source build still occupies the slot until the builder acknowledges
HTTP 408 during upload Retry on a faster link; nothing was stored
the artifact store did not answer in time (static site) A fault on our side, not in your archive: storing the unpacked files stalled. Nothing from that attempt is served, and whatever was live keeps serving. Deploy the same archive again
This app has been disabled by Tori. (HTTP 403 app_disabled) Nothing in the deploy can fix it: Tori has disabled the app, and rollbacks are refused too. See Disabled apps and accounts
Plan cap (app limit reached, concurrent deploy cap) Delete an app you do not need, wait for an in-flight deploy, or stay within the plan whoami shows

Fix the cause, then deploy again: npx tori.host for a static site or a source-built service, or npx tori.host deploy --image … for a digest you brought. There is no “retry this id” command: a new deploy is a new id.

In the console, deploys can roll back a previous attempt whose artefacts are still present. For a static site, your plan keeps the files of a fixed number of deploys before the live one — limits.rollbackDeploys on GET /api/account, and the rollback dialog quotes it. Files of older deploys are removed, and Tori cannot rebuild them: a static site is built on your machine, so the only way back to an older version is to deploy it again. A deploy whose files have been reclaimed cannot be rolled back; the API refuses and the console does not offer it. Neither can a static deploy made before the platform recorded a checksum for each upload: the console marks it predates checksums, the deploy list reports it with predatesArtifactDigest: true, and the API refuses it with HTTP 400. Rolling back a service deploy that had a release command runs that same command as a Job before traffic moves; if the Job fails, the rollback does not switch the revision.

A static site is served from the zone’s own copy of its files. If the deploy that is live has no files the zone can copy — for example a deploy made before the platform recorded a checksum for each upload — the zone does not switch to it. It keeps serving the deploy it was already serving, or serves nothing if it had none, and reports that back.

The console’s app overview then says so under the app card: The zone is serving deploy d_b, not d_a., or The zone is serving nothing for this app, followed by the reason. current deploy on the card still names the live deploy, which is what a rollback or the CLI acts on.

Deploy again to replace it. The notice disappears as soon as the new deploy is live; the zone switches to it once it has copied its files, usually within seconds, and until then keeps serving what it served before. If it says Last reported …, the zone has not confirmed it for several minutes, so it is what the zone last said rather than what it says now.

Over HTTP, GET /api/apps/<slug> carries the same as servingMismatch: servingDeployId (null when the zone serves nothing for the app), reason (noArtifact today), since, reportedAt and stale. It is null when the zone serves currentDeployId, or has reported nothing about the app.

Terminal window
curl -sS -H "Authorization: Bearer $TORI_TOKEN" \
https://api.tori.host/api/apps/<slug>/deploys
Terminal window
curl -sS -H "Authorization: Bearer $TORI_TOKEN" \
https://api.tori.host/api/apps/<slug>/deploys/<deployId>
Terminal window
curl -sS -X POST -H "Authorization: Bearer $TORI_TOKEN" \
https://api.tori.host/api/apps/<slug>/deploys/<deployId>/cancel

Operation ids: listDeploys, getDeploy, watchDeploy, cancelDeploy, rollbackApp in the API reference.

Download this page as Markdown.