When a deploy fails: find the cause and roll back

July 30, 2026
Guide

A deploy on JetDeploy goes through three steps: build, pre-deploy script, rollout. It can fail in any of them, and which one failed decides two things: whether your users noticed anything, and where to look for the cause. This guide covers both, and how to put the previous version back when you need to.

Deployoperation FAILUREBuildfailed or > 1200 sPrevious version keeps servingPre-deploy scriptfailed or > 510 sPrevious version keeps servingNot ready in timeexits · OOM · no portRollout goes onnew version takes over when ready
Only a readiness failure lets the new version go live; the other two leave the previous one serving.

Step 1: which step failed

The operation ends in FAILURE, and its error_message already says a lot. Read it on the App page, or through the API:

curl -sS -H "Authorization: Bearer $JD_TOKEN" \
  https://jetdeploy.com/api/v1/operations/871 | jq '{status, error_message, commit}'
Failed step The previous version error_message carries
Build, or build over 1200 s keeps serving the failing build phase and the last lines of the build log
Pre-deploy script, or over 510 s keeps serving the exit code of the last attempt and the last lines of its output
New version not ready in time the rollout goes on; the new version takes over once ready per process: it exits, it runs out of memory, or nothing listens on its port

In the first two, nothing new went live. A pre-deploy script that failed halfway can still have changed the database, though, and the previous version keeps serving on top of it: see Database migrations in the pre-deploy script. The third case needs attention, because the new version is on its way in.

Step 2: read the right logs

Build and pre-deploy: the deploy logs

The deploy logs of a run hold three streams: build, predeploy and outcome. Read a page of them:

curl -sS -H "Authorization: Bearer $JD_TOKEN" \
  "https://jetdeploy.com/api/v1/apps/42/deploy-logs?operation=871" | jq -r '.rows[] | "\(.stream)\t\(.message)"'

What to look for:

  • Build: the first error, not the last line. With npm, pip or go build, the real cause is usually a few lines above the final summary.
  • Pre-deploy: each attempt starts with a Pre-deploy attempt N line. A traceback there is from your migration or script, run with the App's environment variables.

Readiness: the runtime logs

When the new version does not become ready, the cause is in the runtime logs of the process named in the error. Filter by level to skip the noise:

curl -sS -H "Authorization: Bearer $JD_TOKEN" \
  "https://jetdeploy.com/api/v1/apps/42/runtime-logs?pod=7&level=error&level=warn"

pod takes the id of the process, as listed in GET /api/v1/apps/42. Repeat level for several levels; a comma-separated value is refused.

The three causes the error message names, and what usually produces them:

  • The process exits. A missing environment variable read at startup, a failed connection to a database that is not ready, or a typo in the process command.
  • It runs out of memory. The process is killed for using more memory than it may, often at startup from too many worker processes: gunicorn --workers, puma workers, Node cluster mode.
  • Nothing listens on its port. The server binds 127.0.0.1 instead of 0.0.0.0, or a port other than the container port. JetDeploy sets no PORT variable, so a server that reads PORT falls back to its own default unless you set it.

Make levels reliable. On a plain text line the first matching rule wins, and info anywhere in the line comes first: an [ERROR] line mentioning /userinfo is filed as info and level=error misses it. Log JSON with a level field set to a word ("error", not pino's 50), and the filter reads that field instead of guessing from the text.

Step 3: fix forward or roll back

Most failures in the build or the pre-deploy script are fixed forward: correct the code, push again. The previous version is still serving, so there is no hurry.

When a new version went live and misbehaves, roll back. Through the API, list the releases:

curl -sS -H "Authorization: Bearer $JD_TOKEN" \
  https://jetdeploy.com/api/v1/apps/42/releases | jq '.[] | {id, commit, deployed_at, current, available}'

Then roll back to one that is available:

curl -sS -X POST -H "Authorization: Bearer $JD_TOKEN" -H 'Content-Type: application/json' \
  -d '{"release": 318}' https://jetdeploy.com/api/v1/apps/42/rollback

It answers 202 with an apply operation, which you follow like any other. From an AI agent, the same calls are the list_releases and rollback_app tools.

What a rollback does and does not do:

  • It puts the image of that release live with the configuration stored now: environment variables, processes, domains and redirect rules as they are saved.
  • It does not run the pre-deploy script, and does not reverse database migrations. The earlier code has to work with the current schema.
  • The registry keeps the image the App runs plus the last three builds. Older releases show available: false and cannot be rolled back to.
  • The next push deploys the new commit as usual.

If a deploy is still running and you want it stopped, POST /api/v1/operations/<id>/cancel stops it cleanly before the pre-deploy script starts. Once the script has finished, the new version is already rolling out and Cancel only ends the operation.

A short runbook

  1. Open the failed operation and read error_message.
  2. Build or pre-deploy failed? The old version is serving. Read the deploy logs, fix, push.
  3. Readiness failed? Read the runtime logs of the named process, filtered to errors.
  4. Is the new version live and broken? Roll back to the last good release, then fix forward.
  5. Did the failed deploy include a migration? Check that the previous version works with it before rolling back.

Next step

Most readiness failures come from the process itself: its bind address, its port, its memory at startup. Graceful shutdown in Go and Next.js standalone builds show a correct setup for two common stacks, and Database migrations in the pre-deploy script covers schema changes a rollback can live with.

Related Articles

All posts