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.
npx tori.host deploy statusnpx tori.host deploy status d_examplenpx tori.host deploy watch d_exampleSee tori deploy for flags. In the console, open the app → deploys.
What the CLI prints
Section titled “What the CLI prints”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.
Reading the phase timings
Section titled “Reading the phase timings”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.
Status
Section titled “Status”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.
Watching and interrupting
Section titled “Watching and interrupting”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.
Cancel
Section titled “Cancel”npx tori.host deploy cancel d_exampletori 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.
Failures and recovery
Section titled “Failures and recovery”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.
When the zone serves a different deploy
Section titled “When the zone serves a different deploy”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.
curl -sS -H "Authorization: Bearer $TORI_TOKEN" \ https://api.tori.host/api/apps/<slug>/deployscurl -sS -H "Authorization: Bearer $TORI_TOKEN" \ https://api.tori.host/api/apps/<slug>/deploys/<deployId>curl -sS -X POST -H "Authorization: Bearer $TORI_TOKEN" \ https://api.tori.host/api/apps/<slug>/deploys/<deployId>/cancelOperation ids: listDeploys, getDeploy, watchDeploy, cancelDeploy, rollbackApp in the API reference.
Download this page as Markdown.