Rollback Strategies and Manual Approval Gates
Every deploy this section has built moves forward — new commit, new image, new task definition revision, rolled out. None of them have covered what happens when the thing you just promoted to production turns out to be broken. This chapter adds that missing direction: a workflow whose entire purpose is to move a service backward, on purpose, to a revision you already know was good.
Why This Isn’t Just “Deploy the Old Code Again”
You could, in principle, revert the commit and let the normal pipeline rebuild and redeploy the old version — but that takes as long as any other deploy, going through the build step again for code that’s already been built once. ECS already has every image you’ve ever deployed sitting in previous task definition revisions, each one immutable and still registered even after a newer one takes over. Rolling back is just pointing a service at an older revision that’s already known-good, with no rebuild involved at all.
List what’s available for a given service before you roll anything back:
aws ecs list-task-definitions --family-prefix devstuffs-hello --sort DESC --region us-east-1This returns every revision ARN, newest first — devstuffs-hello:14, devstuffs-hello:13, and so on. You’ll pass one of these revision numbers into the workflow.
A Gate Built for Speed, Not Delay
The production environment from a couple chapters back pairs a required reviewer with a wait timer — appropriate for a routine deploy, where a few minutes of buffer is a reasonable cost for catching mistakes before they ship. A rollback is the opposite situation: something is already broken, and every extra minute is minutes of continued impact. This chapter uses a separate environment with a reviewer but deliberately no wait timer:
- Settings → Environments → New environment, name it
rollback. - Enable Required reviewers only — leave the wait timer off entirely.
Same category of gate — a human confirms it before it runs — but tuned for how a rollback actually needs to behave: reviewed, but immediate.
The Workflow
name: Rollback ECS Service
on:
workflow_dispatch:
inputs:
target:
description: 'Which tier to roll back'
required: true
type: choice
options: [dev, staging, production]
revision:
description: 'Task definition revision number to roll back to (e.g. 12)'
required: true
type: string
permissions:
contents: read
jobs:
rollback:
runs-on: ubuntu-latest
environment:
name: rollback
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Resolve service name for target tier
id: resolve
run: |
case "${{ inputs.target }}" in
dev) echo "service=devstuffs-service" >> "$GITHUB_OUTPUT" ;;
staging) echo "service=devstuffs-service-staging" >> "$GITHUB_OUTPUT" ;;
production) echo "service=devstuffs-service-production" >> "$GITHUB_OUTPUT" ;;
esac
- name: Confirm the target revision exists
run: |
aws ecs describe-task-definition \
--task-definition "devstuffs-hello:${{ inputs.revision }}" \
--query 'taskDefinition.{family: family, revision: revision, image: containerDefinitions[0].image}'
- name: Roll the service back
run: |
aws ecs update-service \
--cluster devstuffs-cluster \
--service ${{ steps.resolve.outputs.service }} \
--task-definition "devstuffs-hello:${{ inputs.revision }}" \
--region us-east-1A few choices here worth calling out:
workflow_dispatchwith typed inputs is what gives you the dropdown-and-text-box form on the Actions tab —type: choicerenderstargetas a dropdown limited to exactlydev,staging, orproduction, so there’s no way to typo a tier name and silently roll back nothing.- The
casestatement mappingtargetto a real service name exists because your three services from the last chapter aren’t named with a predictable pattern (devstuffs-service, notdevstuffs-service-dev) — this is the one place that inconsistency has to be reconciled, and doing it explicitly here is clearer than trying to paper over it with string interpolation tricks. - The “confirm the revision exists” step runs before the actual rollback and fails loudly (a nonexistent revision number makes
describe-task-definitionerror out) rather than letting a typo’d revision number reachupdate-service, where the failure mode would be a rejected API call after the reviewer has already approved the run. - No
render-task-definitionstep this time. Rolling back doesn’t need to edit anything — the whole point is deploying a task definition exactly as it already exists, untouched.update-service --task-definition <family>:<revision>is the entire mechanism.
Running a Rollback
From the Actions tab, select this workflow, click Run workflow, pick a tier from the dropdown, and type in a revision number from the list-task-definitions output earlier. The job pauses on the rollback environment’s reviewer gate exactly like deploy-production did in earlier chapters — approve it, and the rollback runs immediately, no timer to wait out.
Verify the same way you’ve verified every deploy in this section: look up the service’s current task public IP and curl it, or check ECS console → Clusters → devstuffs-cluster → Services → (the tier) → Deployments to confirm the active revision number matches what you rolled back to.
Warning
update-service --task-definition here targets a specific revision number you already know is good — it does not “undo the last deploy” automatically or look up history for you. If you don’t already know which revision was working, list-task-definitions combined with checking each one’s image field (as the confirmation step above does) is how you’d figure that out before running this workflow at all.