Operate
What you get: the running state read back, the logs, and the previous revision restored.
Ship a second revision 🚀
Section titled “Ship a second revision 🚀”Change one value in the complete
deploy.ymlfrom chapter 2:version: 2service: hellorole: webimage: traefik/whoami:v1.10hosts: [root@box.example.com]env:vars:BUNDANT_DEMO: "2"healthcheck:path: /timeout: 30proxy:enabled: truehost: hello.bundant.testport: 80Every commit is a revision, because the revision is the git short sha of the directory holding
deploy.yml. Commit and deploy:Terminal window git add deploy.ymlgit commit -m 'change the demo env'bundant deploy -f deploy.ymldeploy hello/web rev=8de4a23 -> root@box.example.com[root@box.example.com] deploying default.hello.web.8de4a23[root@box.example.com] pull skipped (present, policy=missing)[root@box.example.com] network ok[root@box.example.com] run ok: default.hello.web.8de4a23[root@box.example.com] health gate passed (1 target(s))[root@box.example.com] proxy-ensure ok[root@box.example.com] proxy-swap ok[root@box.example.com] stop-old okdeployed default.hello.web.8de4a23 on 1 host(s)Read it back 📊
Section titled “Read it back 📊”A unit is one addressable thing bundant manages — a service or an accessory — and a selector is the path you pass to a flat verb:
group.service.rolefor a service,group.servicefor an accessory, matched as a glob where*is one segment and**is any number.bundant status '*' -f deploy.ymlprints one line per unit:Terminal window bundant status '*' -f deploy.ymldefault.hello.web rev=8de4a23 live=1/1 [default.hello.web.8de4a23]--jsonprints the same unit fields as one object:Terminal window bundant status '*' -f deploy.yml --json{"ok":true,"command":"status","data":[{"path":"default.hello.web","live":1}]}Terminal window bundant logs '*' -f deploy.yml --tail 5default.hello.web.8de4a23 | 2026/10/20 21:31:47 Starting up on port 80Each line is prefixed with the container it came from, which matters once a service has more than one copy (a replica, covered in chapter 4).
bundant historylists every retained revision, newest first — the timestamps and theUp/Exitedages are from the run that produced this page, so yours will show different ones:Terminal window bundant history -f deploy.yml== root@box.example.com (hello-web) ==* default.hello.web.8de4a23 1 replica Up 2026-10-20 21:31:47 +0000 UTCdefault.hello.web.0f2c8cc 1 replica Exited (2) 1 second ago 2026-10-20 21:31:44 +0000 UTCdefault.hello.web.11a45b2 1 replica Exited (2) 3 seconds ago 2026-10-20 21:31:42 +0000 UTC* = running revisionRoll back ⏪
Section titled “Roll back ⏪”Rollback means starting a retained revision and pointing the proxy back at it.
bundant rollbackwith no target goes to the previous revision: it starts the old container, runs the same health gate, swaps the proxy, and stops the current one:Terminal window bundant rollback -f deploy.yml[root@box.example.com] rollback: default.hello.web.8de4a23 -> default.hello.web.0f2c8cc (1 replica(s) as deployed)[root@box.example.com] health gate passed on default.hello.web.0f2c8cc[root@box.example.com] rolled back to default.hello.web.0f2c8cc (1 replica(s))With no target, rollback always goes to the previous revision, so you do not need the sha for the common case. Use
--to-revision <sha>only when you want an older one.Terminal window bundant status '*' -f deploy.ymldefault.hello.web rev=0f2c8cc live=1/1 [default.hello.web.0f2c8cc]Terminal window bundant history -f deploy.yml== root@box.example.com (hello-web) ==default.hello.web.8de4a23 1 replica Exited (2) Less than a second ago 2026-10-20 21:31:47 +0000 UTC* default.hello.web.0f2c8cc 1 replica Up 2026-10-20 21:31:44 +0000 UTCdefault.hello.web.11a45b2 1 replica Exited (2) 6 seconds ago 2026-10-20 21:31:42 +0000 UTCThe timeline is the point of the design: the old container is stopped, never removed, so rollback is a start, not a rebuild. The stopped revisions wait until
bundant prunekeeps the newest--keep(default 3) and removes the rest.flowchart TD r1["11a45b2 · first revision<br/>stopped, kept"] -- "deploy" --> r2["0f2c8cc · second revision<br/>healthy, live"] r2 -- "deploy" --> r3["8de4a23 · third revision<br/>live"] r3 -- "rollback, no target" --> r4["0f2c8cc · live again"] subgraph notes["notes"] direction LR kept["older revisions<br/>stay stopped"] n1["starts the old one;<br/>old stays live<br/>until the swap"] kept ~~~ n1 end r1 -. "prune keeps newest 3" .-> kept r4 -. "rollback starts it again" .-> n1 classDef ok fill:#252925,color:#00c853,stroke:#00c853; classDef risk fill:#252925,color:#fc5d23,stroke:#fc5d23; classDef storage fill:#252925,color:#ae81ff,stroke:#ae81ff; classDef neutral fill:#2e332e,color:#d9d9d4,stroke:#494848; class r2,r3 ok; class r4 risk; class kept storage; class r1,n1 neutral;Colour legend: blue = an action on your machine or a normal step · green = healthy or live · orange = the one irreversible step · purple = storage and state.
To pick an exact revision, use
bundant rollback --to-revision <sha> -f deploy.yml. A revision is retained untilbundant pruneremoves it or you delete the service.--to-revisionstarts any retained revision, including one older than the current schema or data migration: the container goes back, its volumes do not, so check what the old revision expects before you point traffic at it.You should see
rolled back to default.hello.web.0f2c8ccand, instatus, the revision afterrev=change from8de4a23back to0f2c8cc.
What actually happens ⚙️
Section titled “What actually happens ⚙️”deploy takes a per-service mkdir lock on the first host — ~/.bundant/locks/default/hello.lock,
holding an owner line and reclaimed after BUNDANT_LOCK_TTL_S — so two deploys cannot interleave.
Each container carries bundant.revision and bundant.replica labels; status, history,
scale and rollback read those labels rather than a local file. rollback selects the
previous revision (or --to-revision), starts the stopped container, re-runs the health gate, does
one proxy deploy, then stops the revision that was live. Commands and exit codes are in the
CLI reference; the sequence is in
Architecture.