Releases and rollbacks
What a release records, how a failed deploy rolls back on its own, what dockflow rollback restores, and how an interrupted run is repaired.
Every deploy that changes something creates a release: a numbered record, kept on the server, of exactly what the app ran. A failed deploy goes back to the previous release on its own, and dockflow rollback re-applies any release still kept, without a rebuild.
What a release records
Releases are numbered per environment: 1, 2, 3, and so on, never reused. Each one is a file on the server, /var/lib/dockflow/apps/<app>-<env>/releases/<seq>.json.gz. The agent writes it, and never changes it once the run is over. It records:
- the desired app: every service with its full container spec, as sent to Docker (image pinned by digest, environment, ports, mounts, networks, healthcheck, restart policy), plus its strategy, replicas and routes;
- the uploads: each destination with the digest of its file manifest;
- which release serves each service: a service that a deploy left unchanged keeps the containers of an older release;
- its version label, the git commit and branch, who deployed it, from which machine or CI provider, and when;
- what made it:
deploy,rollback,restart,stop,scaleordestroy(of some services); - its status, and the error when the run failed.
Values interpolated into the environment of a container, secrets included, are part of its spec, as they are in docker inspect. That is why release files, like all of /var/lib/dockflow, are readable by root only.
Statuses
| Status | Meaning |
|---|---|
applying | The run is in progress |
active | The current release |
superseded | A release that was active before the current one |
rolled_back | The run failed and the previous release was restored |
failed | The run failed and was left in place (on_failure: fail, --no-rollback), or its rollback failed |
abandoned | The agent stopped before the release went live; found by the watchdog or the next run, which restored the previous release |
Only active and superseded releases went live, so only they can be restored with dockflow rollback.
Version labels
Besides its number, a release carries a free label, version. dockflow releases and dockflow version <env> show it, and the configuration can use it as ${DOCKFLOW_VERSION}. It is the first of:
--version <label>ondockflow deploy;- in CI, for a run started by a tag, the tag without its environment suffix:
1.2.0for the tags1.2.0and1.2.0-staging; - the short git commit, suffixed
-dirtywhen the working tree has uncommitted changes; - outside git, the time of the deploy in UTC, such as
20261026-140311.
Two releases may share a label (a redeploy of the same commit after a configuration change); the number tells them apart.
Retention
After each successful run, the agent keeps:
- the newest
releases.keepreleases that went live (activeorsuperseded), 10 by default; - every release newer than the oldest of those, whatever its status.
It deletes the other release files. Then it deletes the images, the server-local registry's manifests and the previous upload contents that no kept release uses. So every kept release can be restored from what is on the server.
version: 1
app: shop
releases:
keep: 5 # at least 2
environments:
production: {}The registry's storage is freed separately by dockflow prune --registry. See Cleanup.
When a deploy fails
A deploy keeps the old containers until it commits. New containers start next to them (or after them, with stop-first). Until the release becomes active, the old containers are only stopped, never removed. So when something fails before the commit, the agent:
- starts the old containers again if they were stopped, and waits until they are healthy;
- points the switch back at them, which takes about a second for routed services;
- puts back the files uploads had changed;
- removes the new containers.
The release ends rolled_back, the previous one is still active, and the command exits with code 8. after_failure hooks run, and webhooks are sent.
What counts as a failure:
- a new container exits, restarts three times (a crash loop), becomes unhealthy, or is not healthy within its timeout (Rollouts);
- a published port is already in use;
- a
beforeorafterjob exits with a non-zero code, or runs longer than its timeout; - a fatal hook fails;
- an internal or public HTTP check fails, unless
on_failuresays otherwise (see below); - the release cannot be recorded as live on the server, at the very end (a full disk):
state.commit_failed; - the run is cancelled with Ctrl+C or
dockflow cancel: it rolls back and exits with code 130.
A deploy reports a failure only once the previous release serves again. A deploy reported failed never leaves the new release serving.
The other way round, once the release is recorded as live (one write, the commit), it stays live. If the agent cannot finish what is left (marking the previous release superseded, removing the old containers), the next run completes it.
Some failures happen before anything changed on the server. They stop the run with nothing to undo:
- a build that fails (exit code 7);
- a fatal
before_buildorbefore_deployhook (exit code 11); - a lock held by another run (exit code 5).
If the rollback itself fails, the release ends failed, the environment is marked degraded, and the command exits with code 9. dockflow status shows the flag until a run succeeds.
A fatal after_deploy hook, on your machine or the server, fails after the release became active. The CLI then runs dockflow rollback to the previous release.
on_failure
on_failure decides what a failed HTTP check does: the internal check of a service (x-dockflow.health.http) or a public endpoint (health.endpoints). Set it in dockflow.yml, or per environment.
| Value | A failed HTTP check |
|---|---|
rollback (default) | Rolls the whole app back to the previous release, as any other failure |
fail | Leaves the run where it stopped, with the release marked failed; the environment is degraded; exit code 9 |
ignore | Is reported as a warning; the deploy goes on and the release becomes active |
version: 1
app: shop
on_failure: rollback
environments:
production: {}
staging:
on_failure: ignorefail is for looking at a failed release, not for keeping it. The release stays in place only until the next run that takes the environment's lock:
- That run, whatever it is (a deploy, a rollback, but also a
restartor ascale), first rolls the failed release back to the current one, then plans from it. dockflow planwarns about it (run.unfinished).- The watchdog leaves it alone.
To keep the new version, fix the check and deploy again.
A failure of the rollout itself always rolls back, whatever on_failure says: a container that crashes, stays unhealthy or times out, or a failed job. A service left half-switched is never useful.
dockflow deploy --no-rollback behaves as on_failure: fail for one run, to debug a failing check. See Rollouts.
dockflow rollback
dockflow rollback production # the previous release
dockflow rollback production --to 41 # a given release
dockflow rollback production --to 1.4.0 # the newest release of a version
dockflow rollback production --to 41 --service web # only web, as release 41 had it--to takes either:
- a release number, as listed by
dockflow releases; - or a version label, for the newest release of that version that went live.
The target must be a release that went live (active or superseded), still kept, and not the current one.
A rollback is a deploy whose desired state is the recorded release, so it goes through the same plan and rollout as a deploy:
- every service gets back the exact spec of that release, image digest included;
- services added since are removed, and services removed since are created again;
- uploads get back the files of that release, kept on the server by content;
- unchanged services keep their containers, and routed services switch without downtime.
The images are still on the server as long as the release is kept, so nothing is built, pushed or downloaded. A rollback works even when your registry or your CI is down.
A rollback always moves forward: it creates a new release with the target's version label, shown as active (rollback to 41). To undo a rollback, run dockflow rollback again.
With --service, only that service goes back; the others keep what they run now, and the new release keeps the current version label.
A rollback runs no jobs. It does not replay old migrations, and it does not undo the ones the newer release ran. It does not restore the data in volumes either. If a release changed a database schema, check that the previous version of your app works with it before rolling back.
Interrupted runs
The agent can die in the middle of a run: the server reboots, Docker restarts, its container is killed or runs out of memory. Its release then stays applying, and the kernel releases the lock. Nothing is left locked.
What happens next depends on one write, the commit. The agent records the release as live in env.json once everything else is done.
- Before the commit, the run is rolled back. Its release ends
abandoned, and the previous release serves again. - After the commit, the release is live and stays so. It is marked
active, the previous onesuperseded, and the old containers are removed.
The watchdog does this within 10 seconds of the agent's container stopping. That matters for stop-first services: the dead run had stopped the old container on purpose, so Docker does not start it again on its own.
Without the watchdog (server init --no-watchdog), the next run that takes the lock (a deploy, a rollback, a restart…) does it first. Either way:
- the containers and jobs the dead run had created are removed;
- the containers of the current release get their names back, and those the dead run had stopped start again;
- the switch points back at them, and the current release's uploads are restored; a routed service's new containers are removed only after that, so it keeps serving;
- the unfinished release is marked
abandoned; - then the next run plans from what is actually running, as usual.
A plan always starts from the actual state, so the next deploy brings the app to the desired state whatever the dead run had done.
Losing the CLI or the CI runner interrupts nothing: the agent finishes the run on its own.
Seeing the history
dockflow releases productionSEQ VERSION STATUS CREATED BY
43 1.8.1 active 2026-10-26T14:20:05Z alice ← current
42 1.8.0 rolled_back 2026-10-26T14:11:47Z alice
41 1.7.2 superseded 2026-10-25T09:02:13Z ci-bot| Option | Effect |
|---|---|
-n, --count <n> | Show the n most recent releases |
--stats | Add durations and the success rate |
dockflow status production shows the current release, its services and containers, and the degraded flag. With --json, both print one JSON document.
How it works
The pieces of a Dockflow deploy, from the CLI on your machine to the agent, the switch, the registry and the state on the server.
Rollouts
How Dockflow replaces the containers of a service, the three strategies and how each service gets one, health gating, draining, timeouts and what happens on failure.