Skip to content

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-1

This 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:

  1. Settings → Environments → New environment, name it rollback.
  2. 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-1

A few choices here worth calling out:

  • workflow_dispatch with typed inputs is what gives you the dropdown-and-text-box form on the Actions tab — type: choice renders target as a dropdown limited to exactly dev, staging, or production, so there’s no way to typo a tier name and silently roll back nothing.
  • The case statement mapping target to a real service name exists because your three services from the last chapter aren’t named with a predictable pattern (devstuffs-service, not devstuffs-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-definition error out) rather than letting a typo’d revision number reach update-service, where the failure mode would be a rejected API call after the reviewer has already approved the run.
  • No render-task-definition step 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.

Last updated on