Docs
/
AWS Cloud
Chapter 13

13 — CI/CD on AWS

CI/CD Pipeline Overview

Code Push → Build → Test → Deploy

AWS Services:
  CodePipeline  → Orchestrates the pipeline
  CodeBuild     → Build & test
  CodeDeploy    → Deploy to EC2/ECS/Lambda
  ECR           → Docker image registry

CodeBuild

# buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 20
    commands:
      - npm ci

  pre_build:
    commands:
      - echo Logging in to ECR...
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_REGISTRY
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:-latest}

  build:
    commands:
      - npm run test
      - npm run build
      - docker build -t $ECR_REGISTRY/$ECR_REPO:$IMAGE_TAG .
      - docker push $ECR_REGISTRY/$ECR_REPO:$IMAGE_TAG

  post_build:
    commands:
      - echo Writing image definitions file...
      - printf '[{"name":"api","imageUri":"%s"}]' $ECR_REGISTRY/$ECR_REPO:$IMAGE_TAG > imagedefinitions.json

artifacts:
  files:
    - imagedefinitions.json

cache:
  paths:
    - node_modules/**/*

CodePipeline

Source (GitHub/CodeCommit)
    ↓
Build (CodeBuild)
    ↓
[Manual Approval] (optional, for production)
    ↓
Deploy (ECS / Lambda / S3 / EC2)

GitHub Actions with AWS

# .github/workflows/deploy.yml
name: Deploy to ECS

on:
  push:
    branches: [main]

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: my-app
  ECS_CLUSTER: production
  ECS_SERVICE: api

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456:role/GitHubActionsRole
          aws-region: ${{ env.AWS_REGION }}

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

      - name: Build & push image
        env:
          ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

      - name: Update ECS task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: api
          image: ${{ steps.ecr-login.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}

      - name: Deploy to ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true

ECR (Elastic Container Registry)

# Create repository
aws ecr create-repository --repository-name my-app

# Login
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456.dkr.ecr.us-east-1.amazonaws.com

# Build, tag, push
docker build -t my-app .
docker tag my-app:latest 123456.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0
docker push 123456.dkr.ecr.us-east-1.amazonaws.com/my-app:1.0.0

# Enable image scanning
aws ecr put-image-scanning-configuration \
  --repository-name my-app \
  --image-scanning-configuration scanOnPush=true

# Lifecycle policy (keep last 10 images)
aws ecr put-lifecycle-policy --repository-name my-app \
  --lifecycle-policy-text '{"rules":[{"rulePriority":1,"selection":{"tagStatus":"untagged","countType":"imageCountMoreThan","countNumber":10},"action":{"type":"expire"}}]}'

Deployment Strategies

StrategyHowRollback
RollingReplace instances graduallyRedeploy previous version
Blue/GreenDeploy new, switch traffic at onceSwitch back instantly
CanaryRoute % of traffic to newRemove canary
All-at-onceReplace everythingRedeploy (downtime)

Key Takeaways

  • GitHub Actions + OIDC = best CI/CD for most teams (no long-term AWS keys)
  • Use ECR for private Docker image registry with vulnerability scanning
  • CodePipeline for fully AWS-native CI/CD; GitHub Actions for flexibility
  • Always add manual approval step before production deployment
  • Tag images with git SHA for traceability
  • Enable image scanning on push in ECR