All Articles

Build a Docker CI/CD Pipeline with GitHub Actions and AWS ECR (2026)

Step-by-step guide to building a production CI/CD pipeline that builds, scans, and pushes Docker images to AWS ECR using GitHub Actions.

DevOpsBoysApr 5, 20264 min read
Share:Tweet

This is the most common DevOps task you'll build in your first job: a pipeline that automatically builds your Docker image, scans it for vulnerabilities, and pushes it to ECR every time code is merged.

Here's the complete setup, step by step.


What You'll Build

Push to main branch
       ↓
GitHub Actions triggers
       ↓
1. Checkout code
2. Run unit tests
3. Build Docker image (tagged with git SHA)
4. Scan image for vulnerabilities (Trivy)
5. Authenticate to AWS ECR
6. Push image to ECR
7. Slack notification ✅

Prerequisites

  • GitHub repository with your app
  • AWS account with ECR repository created
  • GitHub repository secrets configured

Step 1: Create ECR Repository

bash
aws ecr create-repository \
  --repository-name myapp \
  --image-scanning-configuration scanOnPush=true \
  --region us-east-1

Note the repository URI: 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp


Step 2: Create IAM User for GitHub Actions

Create an IAM user with minimal ECR permissions:

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload",
        "ecr:PutImage"
      ],
      "Resource": "*"
    }
  ]
}

Better alternative: Use GitHub Actions OIDC (no long-term keys):

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

Step 3: Add GitHub Secrets

Go to Settings → Secrets → Actions:

  • AWS_ACCESS_KEY_ID (if using IAM user)
  • AWS_SECRET_ACCESS_KEY (if using IAM user)
  • AWS_ACCOUNT_ID — your 12-digit AWS account ID
  • AWS_REGION — e.g., us-east-1
  • SLACK_WEBHOOK_URL — for notifications

Step 4: The GitHub Actions Workflow

Create .github/workflows/build-push.yml:

yaml
name: Build and Push to ECR
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
env:
  AWS_REGION: ${{ secrets.AWS_REGION }}
  ECR_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com
  ECR_REPOSITORY: myapp
  IMAGE_TAG: ${{ github.sha }}
 
jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - name: Run unit tests
        run: |
          # Replace with your test command
          npm install
          npm test
 
  build-push:
    name: Build and Push
    runs-on: ubuntu-latest
    needs: test  # Only run if tests pass
    if: github.ref == 'refs/heads/main'  # Only on main branch
 
    steps:
      - name: Checkout
        uses: actions/checkout@v4
 
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}
 
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2
 
      - name: Build Docker image
        run: |
          docker build \
            --tag $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
            --tag $ECR_REGISTRY/$ECR_REPOSITORY:latest \
            --cache-from $ECR_REGISTRY/$ECR_REPOSITORY:latest \
            .
 
      - name: Scan image with Trivy
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
          format: table
          exit-code: 1          # Fail pipeline on CRITICAL vulnerabilities
          severity: CRITICAL
 
      - name: Push to ECR
        run: |
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
 
      - name: Notify Slack on success
        if: success()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "✅ *${{ github.repository }}* deployed to ECR\nImage: `${{ env.IMAGE_TAG }}`\nTriggered by: ${{ github.actor }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
      - name: Notify Slack on failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "❌ *${{ github.repository }}* build failed\nCommit: `${{ env.IMAGE_TAG }}`"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

Step 5: Optimize Build Speed

Use Docker layer caching with GitHub Actions cache:

yaml
- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3
 
- name: Build and push with cache
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    tags: |
      ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ env.IMAGE_TAG }}
      ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

This caches Docker layers in GitHub Actions cache — typically cuts build time by 50-70%.


Verify the Pipeline

After pushing to main:

bash
# Check image was pushed
aws ecr list-images \
  --repository-name myapp \
  --region us-east-1
 
# Pull and verify
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin \
  123456789.dkr.ecr.us-east-1.amazonaws.com
 
docker pull 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest
docker run --rm 123456789.dkr.ecr.us-east-1.amazonaws.com/myapp:latest

What's Next

This pipeline builds and pushes the image. To complete the CD (deployment) side:

  • Update a Kubernetes Deployment image tag automatically
  • Trigger ArgoCD to sync the new image
  • Use ECS task definition update

See: Build CI/CD with GitHub Actions + ArgoCD + EKS


Resources

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments