Sthora
Deploying applications

Deployments and rollback

Deployment history, live build logs, recreate and blue/green, and rolling back to a previous image without a rebuild.

The history

Every deployment is a numbered row on the application, carrying what produced it and what it produced:

Field
numberSequential per application.
statusQUEUED, BUILDING, DEPLOYING, SUCCEEDED, FAILED, CANCELLED.
triggerMANUAL, UPLOAD, ROLLBACK, GIT_PUSH.
commitSha, commitMessage, commitAuthor, gitRefRead back from the clone, not from whatever asked for the deployment.
imageTagWhat a rollback re-applies.
logByteCount, logTailThe size of the build log, and its last 4 KB.
errorMessageWhy it failed, when it did.
startedAt, finishedAt, durationMs

Build logs

GET /deployments/:id/live is a Server-Sent Events stream carrying the session cookie. It is one-way, survives proxies, and reconnects for free — the same mechanism serves live container logs and live metrics.

The full log lives in a size-capped file on the server (DEPLOY_LOG_MAX_BYTES, 16 MB) and is read through GET /deployments/:id/logs. Log lines never reach PostgreSQL beyond the 4 KB tail on the deployment row — that is what keeps a chatty build from bloating the database.

Every line passes through one point on its way out, and that is where environment-variable values are scrubbed.

Deploy strategies

RECREATE (default)

The old containers stop and the new ones start. There are a few seconds of downtime.

BLUE_GREEN

A request rather than a guarantee. The runner suffixes the compose project with a colour, publishes no host port, and brings the new colour up beside the old one before switching the router.

It falls back to RECREATEsaying so in the build log — for any of:

  • a rollback,
  • an application with no verified hostname (there is no router to switch),
  • free memory below DEPLOY_BLUE_GREEN_MIN_FREE_MEMORY_MB (512 MB) beyond what the running version is already using.

Why it is not the default

Blue/green needs both versions resident at once — double the memory of the application being deployed. A 4 GB box cannot promise that, and a deployment that OOM-kills the version that was working is worse than a few seconds of downtime.

Rollback

POST /deployments/:id/rollback re-applies a previous deployment's image with no rebuild. It is cheap, and it is the reason imageRetentionCount exists: each retained version is a full image on disk, and it is what a rollback has to re-apply.

A rollback is itself a deployment, with trigger: ROLLBACK. The history keeps both entries.

Never run docker system prune -a on this box. It removes every image no running container uses — which is exactly the set of images rollback depends on.

Concurrency

QueueConcurrencyWork
build1Fetch source, detect, build the image
deploy2Render compose, up, health-gate, finalise
lifecycle4start / stop / restart / delete / rollback

One in-flight deployment per application, enforced by a Redis lock on the application id. A second request queues behind it or is refused with 409.

Build concurrency stays at 1 because a pnpm install inside a Node image is the memory spike on a 4 GB box, and two at once is an out-of-memory kill.

When a deployment fails

The previous version keeps running. A failed deployment marks itself FAILED with the captured error and changes nothing about what is serving traffic.

A worker killed mid-deployment leaves a row claiming to run. The boot-time reaper marks those FAILED with a message saying the worker restarted — see troubleshooting.

On this page