CI/CD on AWS
AWS provides a suite of developer tools for continuous integration and continuous deployment. You can use all-AWS tools, or integrate AWS with external CI/CD systems like GitHub Actions, GitLab CI, or Jenkins.
The AWS CI/CD Services
Section titled “The AWS CI/CD Services”┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐│ CodeCommit │───►│ CodeBuild │───►│ CodeDeploy │───►│ Your App ││ (source) │ │ (build/test)│ │ (deploy) │ │ (EC2, ECS, │└──────────────┘ └──────────────┘ └──────────────┘ │ Lambda) │ └──────────────┘ └───────────── CodePipeline (orchestrator) ────────────┘| Service | Role | Comparable To |
|---|---|---|
| CodeCommit | Git repository hosting | GitHub, GitLab |
| CodeBuild | Build and test (CI) | GitHub Actions runner, Jenkins agent |
| CodeDeploy | Deploy to EC2, ECS, Lambda | ArgoCD, Spinnaker |
| CodePipeline | Orchestrate the full pipeline | GitHub Actions workflow, Jenkins pipeline |
CodeCommit
Section titled “CodeCommit”CodeCommit is AWS’s managed Git repository service. It integrates natively with IAM for access control.
# Clone a CodeCommit repo (with IAM credentials helper)git config --global credential.helper '!aws codecommit credential-helper $@'git clone https://git-codecommit.us-east-1.amazonaws.com/v1/repos/my-repoIn practice, most teams use GitHub or GitLab instead of CodeCommit (better UI, more features, wider ecosystem). AWS deprecated CodeCommit for new customers in 2024. If you’re starting fresh, use GitHub + AWS integrations.
CodeBuild
Section titled “CodeBuild”CodeBuild runs your builds in managed containers — no build servers to maintain. You define the build steps in a buildspec.yml file.
buildspec.yml
Section titled “buildspec.yml”version: 0.2
env: variables: APP_ENV: production secrets-manager: DB_PASSWORD: prod/db:password # pull from Secrets Manager
phases: install: runtime-versions: python: 3.12 commands: - pip install -r requirements.txt
pre_build: commands: - echo "Running tests..." - pytest --junitxml=reports/tests.xml
build: commands: - echo "Building Docker image..." - docker build -t my-app:$CODEBUILD_RESOLVED_SOURCE_VERSION . - docker tag my-app:$CODEBUILD_RESOLVED_SOURCE_VERSION \ 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
post_build: commands: - echo "Pushing to ECR..." - aws ecr get-login-password | docker login --username AWS --password-stdin \ 123456789012.dkr.ecr.us-east-1.amazonaws.com - docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
artifacts: files: - imagedefinitions.json # used by CodeDeploy/ECS discard-paths: yes
reports: test-results: files: - reports/tests.xml file-format: JUNITXML
cache: paths: - '/root/.cache/pip/**/*' # cache pip packages between buildsBuild Phases
Section titled “Build Phases”| Phase | When It Runs |
|---|---|
install | Install dependencies, runtime versions |
pre_build | Tests, linting, login to registries |
build | Compile, build Docker images, package |
post_build | Push images, generate deployment artifacts |
Environment Variables
Section titled “Environment Variables”| Variable | Source |
|---|---|
| Plaintext | Defined in buildspec.yml or build project config |
| Secrets Manager | env.secrets-manager in buildspec — never in code |
| Parameter Store | env.parameter-store in buildspec |
| Built-in | CODEBUILD_RESOLVED_SOURCE_VERSION (commit SHA), CODEBUILD_BUILD_ID, etc. |
CodeDeploy
Section titled “CodeDeploy”CodeDeploy automates deployments to EC2 instances, ECS services, or Lambda functions with rollback support.
Deployment Strategies
Section titled “Deployment Strategies”| Strategy | How It Works | Best For |
|---|---|---|
| In-place | Stop the app, deploy new version, start | EC2 (accepts brief downtime) |
| Blue/green (EC2) | Launch new instances, shift traffic, terminate old | Zero-downtime EC2 deployments |
| Blue/green (ECS) | Launch new task set, shift traffic, drain old tasks | ECS services |
| Canary (Lambda) | Shift 10% traffic, wait, shift remaining 90% | Lambda (gradual rollout) |
| Linear (Lambda) | Shift 10% every N minutes | Lambda (even more gradual) |
appspec.yml (EC2)
Section titled “appspec.yml (EC2)”version: 0.0os: linuxfiles: - source: / destination: /var/www/myapphooks: BeforeInstall: - location: scripts/stop_server.sh timeout: 60 AfterInstall: - location: scripts/install_deps.sh timeout: 120 ApplicationStart: - location: scripts/start_server.sh timeout: 60 ValidateService: - location: scripts/health_check.sh timeout: 120Lifecycle Event Hooks (EC2)
Section titled “Lifecycle Event Hooks (EC2)”ApplicationStop → BeforeInstall → Install → AfterInstall →ApplicationStart → ValidateServiceIf ValidateService fails, CodeDeploy automatically rolls back.
CodePipeline
Section titled “CodePipeline”CodePipeline orchestrates the full CI/CD workflow — connecting source, build, test, and deploy stages.
Pipeline Structure
Section titled “Pipeline Structure”Source Stage ──► Build Stage ──► Deploy Stage(GitHub) (CodeBuild) (CodeDeploy / ECS)Each stage has actions (e.g. “Build with CodeBuild”), and transitions between stages can require manual approval.
Creating a Pipeline (CLI)
Section titled “Creating a Pipeline (CLI)”aws codepipeline create-pipeline --pipeline '{ "name": "my-app-pipeline", "roleArn": "arn:aws:iam::123456789012:role/CodePipelineRole", "stages": [ { "name": "Source", "actions": [{ "name": "GitHub", "actionTypeId": { "category": "Source", "owner": "ThirdParty", "provider": "GitHub", "version": "2" }, "configuration": { "ConnectionArn": "arn:aws:codestar-connections:...", "FullRepositoryId": "myorg/myrepo", "BranchName": "main" }, "outputArtifacts": [{"name": "SourceOutput"}] }] }, { "name": "Build", "actions": [{ "name": "Build", "actionTypeId": { "category": "Build", "owner": "AWS", "provider": "CodeBuild", "version": "1" }, "inputArtifacts": [{"name": "SourceOutput"}], "outputArtifacts": [{"name": "BuildOutput"}], "configuration": { "ProjectName": "my-app-build" } }] }, { "name": "Deploy", "actions": [{ "name": "Deploy", "actionTypeId": { "category": "Deploy", "owner": "AWS", "provider": "ECS", "version": "1" }, "inputArtifacts": [{"name": "BuildOutput"}], "configuration": { "ClusterName": "production", "ServiceName": "my-app" } }] } ]}'Adding Manual Approval
Section titled “Adding Manual Approval”Insert an approval stage before production deployment:
{ "name": "Approval", "actions": [{ "name": "ManualApproval", "actionTypeId": { "category": "Approval", "owner": "AWS", "provider": "Manual", "version": "1" }, "configuration": { "NotificationArn": "arn:aws:sns:us-east-1:123456789012:approvals", "CustomData": "Review the staging deployment before promoting to production" } }]}GitHub Actions with AWS
Section titled “GitHub Actions with AWS”Many teams prefer GitHub Actions over CodePipeline for its flexibility and developer experience. AWS provides official actions for integration.
Deploying to ECS with GitHub Actions
Section titled “Deploying to ECS with GitHub Actions”name: Deploy to ECS
on: push: branches: [main]
env: AWS_REGION: us-east-1 ECR_REPOSITORY: my-app ECS_CLUSTER: production ECS_SERVICE: my-app
jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write # needed for OIDC 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::123456789012:role/GitHubActionsRole aws-region: ${{ env.AWS_REGION }}
- name: Login to ECR id: ecr-login uses: aws-actions/amazon-ecr-login@v2
- name: Build and push Docker 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: Deploy to ECS uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: task-definition: task-definition.json service: ${{ env.ECS_SERVICE }} cluster: ${{ env.ECS_CLUSTER }} wait-for-service-stability: trueGitHub Actions OIDC (No Access Keys)
Section titled “GitHub Actions OIDC (No Access Keys)”Instead of storing AWS access keys in GitHub secrets, use OIDC federation — GitHub proves its identity to AWS, and AWS issues temporary credentials:
- Create an IAM OIDC identity provider for GitHub.
- Create a role that trusts the GitHub OIDC provider.
- Use
aws-actions/configure-aws-credentialswithrole-to-assume.
No long-lived secrets to rotate or leak.
Deploying Lambda with GitHub Actions
Section titled “Deploying Lambda with GitHub Actions”- name: Deploy Lambda run: | zip function.zip -r . aws lambda update-function-code \ --function-name my-function \ --zip-file fileb://function.zipDeploying to S3 (Static Site)
Section titled “Deploying to S3 (Static Site)”- name: Deploy to S3 run: | aws s3 sync ./dist s3://my-website-bucket --delete aws cloudfront create-invalidation \ --distribution-id E1ABC2DEF3GH \ --paths "/*"Choosing a CI/CD Approach
Section titled “Choosing a CI/CD Approach”| Approach | Best For |
|---|---|
| CodePipeline + CodeBuild | AWS-centric teams, visual pipeline management, approval gates |
| GitHub Actions + AWS actions | GitHub-centric teams, more flexible workflows, multi-cloud |
| GitLab CI + AWS | GitLab-centric teams, built-in container registry |
| Jenkins + AWS | Complex pipelines, existing Jenkins infrastructure |
| ArgoCD (GitOps) | Kubernetes-native, declarative deployments |
Key Takeaways
Section titled “Key Takeaways”- CodeBuild runs builds in managed containers — define steps in
buildspec.yml. Good for building Docker images and running tests. - CodeDeploy handles deployment strategies (in-place, blue/green, canary) with automatic rollback.
- CodePipeline orchestrates source → build → deploy with optional manual approval gates.
- GitHub Actions with OIDC is the most popular alternative — no long-lived AWS credentials, great developer experience.
- Use ECR (Elastic Container Registry) to store Docker images within AWS.
- Always use IAM roles (not access keys) for CI/CD — whether via instance profiles, OIDC, or execution roles.