AWS DevOps — Comprehensive Reference
CodeBuild
buildspec.yml
version: 0.2
env:
variables:
IMAGE_NAME: my-api
secrets-manager:
SONAR_TOKEN: prod/sonar:token
parameter-store:
ECR_REPO: /prod/ecr-repo-url
phases:
install:
runtime-versions:
python: 3.12
nodejs: 20
commands:
- pip install -r requirements-dev.txt
pre_build:
commands:
- echo "Running tests..."
- pytest tests/ --junitxml=test-results/junit.xml --cov=src --cov-report=xml
- aws ecr get-login-password --region $AWS_DEFAULT_REGION |
docker login --username AWS --password-stdin $ECR_REPO
build:
commands:
- IMAGE_TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION::8}
- docker build -t $ECR_REPO/$IMAGE_NAME:$IMAGE_TAG -t $ECR_REPO/$IMAGE_NAME:latest .
- docker push $ECR_REPO/$IMAGE_NAME:$IMAGE_TAG
- docker push $ECR_REPO/$IMAGE_NAME:latest
- printf '[{"name":"api","imageUri":"%s"}]' "$ECR_REPO/$IMAGE_NAME:$IMAGE_TAG" > imagedefinitions.json
post_build:
commands:
- echo "Build complete"
reports:
pytest-coverage:
files: ['coverage.xml']
file-format: COBERTURAXML
test-results:
files: ['test-results/junit.xml']
file-format: JUNITXML
artifacts:
files:
- imagedefinitions.json
- appspec.yaml
- taskdef.json
discard-paths: yes
cache:
paths:
- '/root/.cache/pip/**/*'
- '/usr/local/lib/node_modules/**/*'
CodeBuild CLI
# Start build
aws codebuild start-build --project-name my-api-build
# List recent builds
aws codebuild list-builds-for-project --project-name my-api-build \
--query 'ids[:5]' --output text
# Get build status and logs
BUILD_ID="my-api-build:abc123"
aws codebuild batch-get-builds --ids $BUILD_ID \
--query 'builds[].[id,buildStatus,phases[].phaseStatus]' --output table
# Tail logs
aws logs tail /aws/codebuild/my-api-build --follow
CodeDeploy
appspec.yaml (ECS)
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: api
ContainerPort: 8080
PlatformVersion: LATEST
Hooks:
- BeforeInstall: arn:aws:lambda:us-east-1:123:function:pre-deploy-hook
- AfterInstall: arn:aws:lambda:us-east-1:123:function:smoke-test
- AfterAllowTestTraffic: arn:aws:lambda:us-east-1:123:function:integration-test
appspec.yaml (EC2/On-Premises)
version: 0.0
os: linux
files:
- source: /
destination: /var/www/myapp
hooks:
BeforeInstall:
- location: scripts/stop_server.sh
timeout: 60
AfterInstall:
- location: scripts/install_deps.sh
ApplicationStart:
- location: scripts/start_server.sh
timeout: 30
ValidateService:
- location: scripts/health_check.sh
timeout: 60
Deployment Types
| Strategy | Downtime | Rollback | Use case |
|---|---|---|---|
| In-place | Brief (rolling) | Slow (redeploy) | EC2, non-critical |
| Blue/Green | Zero | Instant (reroute) | ECS, Lambda, prod |
| Canary | Zero | Instant | Lambda aliases, gradual rollout |
| Linear | Zero | Instant | ECS, controlled ramp |
# Create deployment
aws deploy create-deployment \
--application-name my-api \
--deployment-group-name prod \
--deployment-config-name CodeDeployDefault.ECSCanary10Percent5Minutes \
--revision revisionType=S3,s3Location="{bucket=my-bucket,key=deploy.zip,bundleType=zip}"
# List deployments
aws deploy list-deployments \
--application-name my-api \
--deployment-group-name prod \
--include-only-statuses Succeeded Failed \
--query 'deployments' --output text
# Rollback to last successful
aws deploy stop-deployment --deployment-id d-ABC123 --auto-rollback-enabled
CodePipeline
# Get pipeline state (see which stage is failing)
aws codepipeline get-pipeline-state --name my-pipeline \
--query 'stageStates[].[stageName,latestExecution.status,actionStates[].actionName,actionStates[].latestExecution.status]' \
--output table
# Start pipeline
aws codepipeline start-pipeline-execution --name my-pipeline
# Approve a manual approval action
TOKEN=$(aws codepipeline get-pipeline-state --name my-pipeline \
--query 'stageStates[?stageName==`Approval`].actionStates[0].latestExecution.token' \
--output text)
aws codepipeline put-approval-result \
--pipeline-name my-pipeline \
--stage-name Approval \
--action-name ManualApproval \
--token $TOKEN \
--result summary="LGTM",status=Approved
# Retry failed stage
aws codepipeline retry-stage-execution \
--pipeline-name my-pipeline \
--stage-name Build \
--pipeline-execution-id <exec-id> \
--retry-mode FAILED_ACTIONS
ECR (Elastic Container Registry)
# Create repo
aws ecr create-repository \
--repository-name my-api \
--image-scanning-configuration scanOnPush=true \
--encryption-configuration encryptionType=KMS
# Login
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin 123456789.dkr.ecr.us-east-1.amazonaws.com
# Push
docker tag my-api:latest 123456789.dkr.ecr.us-east-1.amazonaws.com/my-api:latest
docker push 123456789.dkr.ecr.us-east-1.amazonaws.com/my-api:latest
# Lifecycle policy (keep last 10 tagged, delete untagged after 7d)
aws ecr put-lifecycle-policy \
--repository-name my-api \
--lifecycle-policy-text '{
"rules": [
{
"rulePriority": 1,
"description": "Delete untagged after 7 days",
"selection": {"tagStatus": "untagged", "countType": "sinceImagePushed", "countUnit": "days", "countNumber": 7},
"action": {"type": "expire"}
},
{
"rulePriority": 2,
"description": "Keep last 10 tagged images",
"selection": {"tagStatus": "tagged", "tagPrefixList": ["v"], "countType": "imageCountMoreThan", "countNumber": 10},
"action": {"type": "expire"}
}
]
}'
# Get scan results
aws ecr describe-image-scan-findings \
--repository-name my-api \
--image-id imageTag=latest \
--query 'imageScanFindings.findings[?severity==`CRITICAL`].[name,severity,uri]' \
--output table
GitHub Actions for AWS (OIDC)
Prefer OIDC over long-lived access keys — no secrets to rotate.
Setup IAM OIDC Provider
# One-time setup per account
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
# Create IAM role with trust policy
aws iam create-role \
--role-name GitHubActionsDeployRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Federated": "arn:aws:iam::123: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:myorg/myrepo:*"}
}
}]
}'
GitHub Actions Workflow
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
permissions:
id-token: write # required for OIDC
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole
aws-region: us-east-1
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image
run: |
docker build -t $ECR_REGISTRY/my-api:${{ github.sha }} .
docker push $ECR_REGISTRY/my-api:${{ github.sha }}
env:
ECR_REGISTRY: 123456789012.dkr.ecr.us-east-1.amazonaws.com
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-def.json
service: my-api
cluster: prod
wait-for-service-stability: true
codedeploy-appspec: appspec.yaml
codedeploy-application: my-api
codedeploy-deployment-group: prod
CodeArtifact
# Create domain and repo
aws codeartifact create-domain --domain myorg
aws codeartifact create-repository \
--domain myorg --repository pypi-store \
--description "PyPI proxy"
# Create upstream connection to public PyPI
aws codeartifact associate-external-connection \
--domain myorg --repository pypi-store \
--external-connection public:pypi
# Login (configures pip)
aws codeartifact login --tool pip --domain myorg --repository pypi-store
# Publish private package
aws codeartifact login --tool twine --domain myorg --repository pypi-store
twine upload dist/*
Deployment Strategies — Decision Guide
Need zero downtime?
├── No → In-place rolling (EC2, EB) — simplest
└── Yes
├── Lambda/API Gateway? → Canary aliases (CodeDeploy)
├── ECS? → Blue/Green (CodeDeploy + ALB)
└── EC2 fleet? → Blue/Green with ASG swap
└── Gradual rollout needed?
├── Yes → Linear10PercentEvery1Minute
└── No → AllAtOnce (with instant rollback)
Guardrails & Gotchas
- CodeBuild privileged mode — required for Docker builds. Only enable when needed; it grants root on the build host.
- GitHub Actions OIDC condition — always restrict
subto your repo.repo:myorg/*:*is dangerously broad. - CodeDeploy ECS blue/green — you need two target groups and a test listener port on the ALB. Forgetting these is the #1 setup error.
- ECR image mutability — set
imageTagMutability=IMMUTABLEfor prod to prevent tag overwriting. Breaks pipelines that push:latest— fix pipelines first. - CodePipeline artifact S3 — artifacts bucket must be in the same region as the pipeline.
- CodeBuild VPC mode — needed to access RDS, ElastiCache, internal services. Requires NAT or VPC endpoints for ECR/S3.
- Lambda canary rollback — CodeDeploy rolls back automatically if the CloudWatch alarm you attach triggers. Wire up your error rate alarm.
- CodeArtifact token expiry — tokens last 12h. In long CI jobs, refresh before
pip installornpm install.