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
| Strategy | How | Rollback |
|---|---|---|
| Rolling | Replace instances gradually | Redeploy previous version |
| Blue/Green | Deploy new, switch traffic at once | Switch back instantly |
| Canary | Route % of traffic to new | Remove canary |
| All-at-once | Replace everything | Redeploy (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