AWS CodeBuild and CodeDeploy Tutorial: Build and Ship on AWS
Learn how to wire AWS CodeBuild and CodeDeploy together to build artifacts, run tests, and deploy to EC2, ECS, or Lambda with blue/green and canary strategies.
What you'll learn
- ✓What CodeBuild and CodeDeploy each do and where the boundary sits
- ✓How to write buildspec.yml and appspec.yml files
- ✓How blue/green and canary deployments work in practice
- ✓Common IAM and artifact-handoff pitfalls
- ✓Production tips for rollback and observability
Prerequisites
- •Comfort with Git, shell scripting, and an AWS compute target like EC2 or ECS
CodeBuild compiles your code, runs your tests, and produces an artifact. CodeDeploy takes that artifact and puts it on running infrastructure safely. Together, with CodePipeline as the glue, they form AWS’s native CI/CD stack — no Jenkins server to babysit, no GitHub Actions minutes to budget, and tight IAM integration with the rest of AWS.
What and Why
CodeBuild is a managed build service. You give it a Docker image (AWS-provided or your own), a buildspec.yml, and a source location. It spins up a container, runs your phases, and uploads artifacts to S3.
CodeDeploy is a managed deployment service. It pulls an artifact and a small manifest (appspec.yml), then orchestrates putting the new version onto your fleet — EC2 instances, ECS tasks, or Lambda aliases. It handles draining, health checks, hooks, and rollbacks.
Why these instead of GitHub Actions or GitLab CI? Three reasons: IAM-native auth (no long-lived tokens), private VPC builds for code that touches internal endpoints, and deep CodeDeploy integration with ALB target groups and ECS services for true blue/green.
Mental Model
A typical pipeline is three stages: Source -> Build -> Deploy. Source watches a Git repo. Build runs CodeBuild and emits an artifact. Deploy hands the artifact to CodeDeploy, which has an opinion about where and how fast to roll it out.
CodeDeploy has three compute platforms with different mechanics. On EC2/on-prem, an agent on each instance pulls and executes lifecycle hooks. On ECS, it shifts traffic between two target groups. On Lambda, it shifts traffic between aliases via weighted routing. The appspec.yml schema differs per platform.
Hands-on Example
A minimal buildspec.yml for a Node.js service that produces a Docker image:
version: 0.2
phases:
install:
runtime-versions: { nodejs: 20 }
commands: [ npm ci ]
build:
commands:
- npm test
- docker build -t $REPO_URI:$CODEBUILD_RESOLVED_SOURCE_VERSION .
- aws ecr get-login-password | docker login --username AWS --password-stdin $REPO_URI
- docker push $REPO_URI:$CODEBUILD_RESOLVED_SOURCE_VERSION
artifacts:
files: [ imagedefinitions.json, appspec.yaml, taskdef.json ]
The accompanying appspec.yaml for ECS blue/green:
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: api
ContainerPort: 8080
GitHub --> CodePipeline (Source)
|
v
CodeBuild
(buildspec.yml)
|
builds & pushes image -> ECR
emits appspec + taskdef -> S3 artifact
|
v
CodeDeploy
(ECS blue/green)
|
+---------+----------+
| |
blue TG (v1, 100%) green TG (v2, 0%)
| |
+-- ALB shifts 10% -> 50% -> 100% -->
|
rollback if alarms trip CodeDeploy will create a green task set, register it with the green target group, shift traffic per your deployment configuration (e.g., CodeDeployDefault.ECSCanary10Percent5Minutes), then drain blue.
Common Pitfalls
- buildspec.yml at the wrong path. It must sit at the repo root unless you override
Source.Buildspec. Otherwise CodeBuild fails immediately with “no buildspec found”. - IAM role too narrow for builds. The CodeBuild service role needs
ecr:GetAuthorizationToken,ecr:BatchCheckLayerAvailability,logs:CreateLogStream, and S3 access to the artifact bucket. Wildcard-permitted starter roles work; least-privilege ones break silently. - appspec.yaml not in artifacts list. CodeDeploy can’t find it and the deployment fails before it starts.
- Missing ALB health checks. Blue/green relies on ALB telling CodeDeploy the green tasks are healthy. Misconfigured health paths cause stuck deployments that eventually roll back.
- CodeDeploy agent off on EC2. For EC2 deployments, the agent must be installed and running. SSM-based provisioning handles this; baked AMIs often miss it.
Production Tips
Always attach CloudWatch alarms to your deployment group — CPU, 5xx rate, latency. CodeDeploy will roll back automatically if any alarm enters ALARM state during the deployment window. This is the single highest-leverage feature; turn it on.
Use deployment configurations that match your risk tolerance. AllAtOnce is fine for dev. Production should be Canary10Percent5Minutes or Linear10PercentEvery1Minute at minimum.
Cache node_modules and Docker layers with CodeBuild’s local cache and S3 cache to cut build times in half. Use VPC-enabled CodeBuild only when you must — it adds a couple of minutes of cold-start ENI provisioning.
Wrap-up
CodeBuild produces artifacts; CodeDeploy puts them on infrastructure with guardrails. Wire them with CodePipeline, write a tight buildspec.yml plus appspec.yml, enable CloudWatch alarm-based rollback, and pick a canary or linear deployment config. You’ll have a production-grade pipeline without managing a single CI server.
Related articles
- AWS AWS Glue ETL Tutorial: Serverless Spark for Data Pipelines
Build serverless ETL jobs with AWS Glue. Learn the Data Catalog, crawlers, Spark and Python shell jobs, partitioning, bookmarks, and how to avoid surprise DPU bills.
- AWS AWS PrivateLink Explained: VPC Endpoints Without the Internet
Understand AWS PrivateLink: interface endpoints, endpoint services, how it differs from VPC peering and Transit Gateway, and when to choose it for private connectivity.
- AWS AWS Secrets Manager Tutorial: Storing and Rotating Secrets
A practical guide to AWS Secrets Manager: creating secrets, retrieving them from apps, automatic rotation, IAM access control, and choosing it over SSM Parameter Store.
- AWS AWS SQS Dead-Letter Queues: Catching Poison Messages
Learn how to configure Amazon SQS dead-letter queues (DLQs) to isolate poison messages, debug consumer failures, and protect your workers from infinite retry loops.