Rollback and the stale-deploy guard
After this you can answer: How do you roll back, and how does CD refuse to deploy an older commit by mistake?
Picture it
Section titled “Picture it”flowchart TB R["deploy run for commit C"] --> S{"started by CI and<br/>main moved on?"} S -->|"yes"| X["refuse: superseded"] S -->|"no"| O{"C older than<br/>SSM deployed-commit?"} O -->|"yes, no allow_older_infra"| Y["refuse: older"] O -->|"no"| G["go on"]How it works
Section titled “How it works”- App rollback. Actions → CD → Run workflow,
image_tag= an older SHA. Publish is skipped. The run checks out main’s newest code, so the infrastructure stays current; only the image changes. - The guard runs first, before any infrastructure command. It reads SSM
/minitickets/dev/deployed-commit, the commit whose infrastructure code CD applied last. - Rule 1, superseded (runs started by CI only): if main’s newest commit is not this run’s commit, refuse. Approve the newest run instead.
- Rule 2, older: if this commit is an ancestor of the recorded one, its infrastructure code is
older than what is live. Refuse, unless a manual run sets
allow_older_infra. - Record. Once an infrastructure apply has started, the run records its commit, even if a later step fails or the run is cancelled.
In MiniTickets
Section titled “In MiniTickets”The guard exists because of a real incident (2026-10-15). A deploy of commit 6ea9575 waited for
approval for days. When it was approved, it applied that old infrastructure code, and Pulumi deleted
10 resources that newer commits had added. The approval page showed which commit, but nothing said
“this one is old”.
The guard script is always read from main’s newest commit, never from the commit being deployed: an old commit may carry an old guard, or none. Recording failed runs matters for the same reason: phase A may already have created resources.
App rollbacks need compatible migrations: the old code runs on the newer schema. The images to roll
back to are kept by the ECR rule for the last 10 deployed-* images.
| From → to | What happens | What allows it |
|---|---|---|
| Release parameters → API task definitiononly when computeEnabled = true | pulumi reads image-tag, so the task definitions use image :<tag> |
|
| CD deploy job → Release parameters | reads deployed-commit for the guard; normally writes image-tag in phase B (phase A if no current image); records deployed-commit at the end if an infrastructure apply ran, including failure or cancellation |
|
See it in AWS
Section titled “See it in AWS”Systems Manager → Parameter Store → /minitickets/dev/deployed-commit → History.
aws ssm get-parameter --name /minitickets/dev/deployed-commit
scripts/deploy-guard.sh (tested by scripts/deploy-guard.test.ts) and
infra/src/release.ts → createRelease.
Break it
Section titled “Break it”| Symptom | Cause | Where to look |
|---|---|---|
| “Deploy guard: refuse old or superseded infrastructure code” fails | Older than the recorded commit, or main has a newer commit | The ::error:: line names which rule |
| A rollback stops at the image check | The old tag is no longer in all three repositories | ECR → images |
| A preview with compute on stops: no image tag | SSM image-tag is still none: CD never deployed | Parameter Store |
Say it in an interview
Section titled “Say it in an interview”Follow-ups
Section titled “Follow-ups”Why not just cancel old waiting runs?
GitHub keeps one waiting run per concurrency group, and it can still be old. A person can approve the wrong one. A server-side check against what is live catches both.
Why record the commit even when the run failed?
From the first apply on, AWS may hold this commit’s resources. If it were not recorded, an older run approved next would pass the guard and delete them.
Why SSM and not a git tag?
The deploy role cannot write to the repository, and AWS is the system of record for what is live. Two standard parameters are free.
Advanced: Details: the two parameters
Pulumi creates /minitickets/dev/image-tag and /minitickets/dev/deployed-commit with the value
none and ignores later changes to the value, so an apply never writes none back. The deploy
role may get and put exactly these two parameters
(infra/src/release.ts → readDeployedImageTag).
Checkpoint
Section titled “Checkpoint”Answer these without scrolling up. If one feels hard, reread that section.
- What is the difference between an app rollback and an infrastructure rollback?
- Which two rules does the guard apply?
- Where does the guard read the last applied commit from?
- Why is the guard read from main’s newest commit?
- What must be true of migrations for an app rollback to work?
Redraw it from memory: run for commit C → superseded? refuse → older than SSM deployed-commit? refuse (unless allow_older_infra) → go on → record C once an apply starts
Try this: on the map, play the deploy flow and open the failure branch of the guard step
Read this book online: https://book.theversehub.in/