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 | |
|---|---|
number | Sequential per application. |
status | QUEUED, BUILDING, DEPLOYING, SUCCEEDED, FAILED, CANCELLED. |
trigger | MANUAL, UPLOAD, ROLLBACK, GIT_PUSH. |
commitSha, commitMessage, commitAuthor, gitRef | Read back from the clone, not from whatever asked for the deployment. |
imageTag | What a rollback re-applies. |
logByteCount, logTail | The size of the build log, and its last 4 KB. |
errorMessage | Why 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 RECREATE — saying 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
| Queue | Concurrency | Work |
|---|---|---|
build | 1 | Fetch source, detect, build the image |
deploy | 2 | Render compose, up, health-gate, finalise |
lifecycle | 4 | start / 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.