DevOps Ninja logo devops.ninja

Deploying to AWS from GitHub Actions: The Production Setup

By DevOps Ninja Editorial · Published 2026-05-09 · // tutorial

The most common GitHub Actions deploy pattern in production: build a container, push to ECR, update an ECS service. This is the working config — copy-pasteable, with the IAM nuances baked in. No mystery; every line earns its keep.

The Working Workflow

name: deploy-prod
on:
  push:
    branches: [main]

permissions:
  id-token: write   # required for OIDC — no AWS access keys
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/gha-deploy
          aws-region: us-east-1

      - name: Login to ECR
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build, tag, push
        env:
          REGISTRY: 123456789012.dkr.ecr.us-east-1.amazonaws.com
          REPO: ninja-app
        run: |
          docker build -t $REGISTRY/$REPO:${{ github.sha }} -t $REGISTRY/$REPO:latest .
          docker push $REGISTRY/$REPO:${{ github.sha }}
          docker push $REGISTRY/$REPO:latest

      - name: Update ECS service
        run: |
          aws ecs update-service \
            --cluster ninja-prod \
            --service ninja-app \
            --force-new-deployment

      - name: Wait for stable
        run: |
          aws ecs wait services-stable \
            --cluster ninja-prod \
            --services ninja-app

The IAM Trust Policy (the part most tutorials skip)

OIDC removes the need for long-lived AWS access keys in GitHub. The trust policy on the IAM role looks like:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
      },
      "StringLike": {
        "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
      }
    }
  }]
}

Why This Works

Common Pitfalls

FAQ

How do I do this for blue/green deploys?

Swap aws ecs update-service for AWS CodeDeploy with aws deploy create-deployment. The CodeDeploy hooks let you run smoke tests before flipping traffic.

What about secrets at runtime?

Use AWS Secrets Manager or SSM Parameter Store and reference them in the ECS task definition's secrets block. Never put secrets in environment variables in the workflow.

// recommended — affiliate Hetzner — Self-Hosted Runners — $8/mo for a runner that beats most paid CI tiers.
// recommended — affiliate DigitalOcean — Build Servers — $200 credit; sized droplets for CI workloads.

Have a correction or a different field experience? We update these pieces. Honest critique welcome.