Manual deployments do not scale. Every manual step is a potential source of error, and the faster your team ships, the more those errors compound. A CI/CD pipeline built on Docker and AWS removes the manual work from the critical path: code gets built, tested, packaged as a container, and deployed to production without human intervention at each stage.
This post walks through a complete pipeline using AWS CodePipeline, AWS CodeBuild, Amazon ECR, and Amazon ECS.
Why Docker and AWS for CI/CD?
Docker containers give you consistent behavior across environments. What runs locally runs the same way in CI and in production. AWS services handle scaling and security at the infrastructure level, and CodePipeline ties the stages together into a single automated workflow.
Prerequisites
- An AWS account with administrative privileges.
- Basic knowledge of Docker and containerization concepts.
- Familiarity with ECS and ECR.
- A sample application (this guide uses Node.js).
Pipeline Architecture
The pipeline has three stages:
- Source: Code is pushed to a version control system (AWS CodeCommit or GitHub).
- Build: CodeBuild builds the Docker image and runs tests.
- Deploy: The image is pushed to ECR and ECS deploys it to a cluster.
Step 1: Setting Up the Source Repository
For seamless AWS integration, AWS CodeCommit is the straightforward choice.
-
Navigate to the AWS CodeCommit console.
-
Create a new repository named
my-app-repo. -
Clone the repository to your local machine:
git clone https://git-codecommit.us-east-1.amazonaws.com/v1/repos/my-app-repo -
Add your application code and push:
git add . git commit -m "Initial commit" git push origin master
Step 2: Containerizing the Application
Create a Dockerfile to define how your application is packaged.
Sample Dockerfile for Node.js
# Use Node.js LTS version as the base image
FROM node:18-alpine
# Set the working directory in the container
WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install --production
# Copy the rest of the application code
COPY . .
# Expose the application port
EXPOSE 3000
# Start the application
CMD ["node", "app.js"]
A few things worth noting: use a minimal base image to keep the image small, copy only necessary files to optimize build caching, and run containers as a non-root user for security.
Step 3: Build Specification for CodeBuild
AWS CodeBuild needs a buildspec.yml file in your repository root.
Sample buildspec.yml
version: 0.2
phases:
pre_build:
commands:
- echo Logging into Amazon ECR...
- $(aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com)
- REPOSITORY_URI=<AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/my-app-repo
build:
commands:
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:latest .
post_build:
commands:
- echo Pushing the Docker image...
- docker push $REPOSITORY_URI:latest
artifacts:
files:
- '**/*'
Replace <AWS_ACCOUNT_ID> with your actual AWS account ID.
Step 4: Setting Up Amazon ECR
Amazon ECR stores your Docker images.
- Navigate to the Amazon ECR console.
- Create a new repository named
my-app-repo. - Note the repository URI for use in
buildspec.yml.
Step 5: Configuring AWS CodeBuild
- Navigate to the AWS CodeBuild console.
- Create a new build project named
my-app-build. - Configure:
- Source Provider: AWS CodeCommit.
- Repository:
my-app-repo. - Environment: Ubuntu Standard runtime, Docker enabled, Privileged mode on (required for Docker builds).
- Buildspec: Use the buildspec.yml from the source code.
Step 6: Setting Up AWS CodePipeline
- Navigate to the AWS CodePipeline console.
- Create a new pipeline named
my-app-pipeline. - Source Stage: AWS CodeCommit,
my-app-repo,masterbranch. - Build Stage: AWS CodeBuild,
my-app-buildproject. - Deploy Stage: AWS ECS,
my-ecs-cluster,my-app-service.
Step 7: Configuring Amazon ECS
Create an ECS Cluster
- Navigate to the Amazon ECS console.
- Create a cluster named
my-ecs-cluster. Use Fargate if you want to avoid managing EC2 instances.
Define a Task Definition
- Go to Task Definitions.
- Create
my-app-taskwith:- Task Role: IAM role with necessary permissions.
- Network Mode:
awsvpcfor Fargate. - Container: name
my-app-container, image from ECR, port 3000 mapped to host 80.
Create a Service
- Go to Services and create
my-app-service. - Launch type: Fargate. Task definition:
my-app-task. Enable rolling updates.
Step 8: Testing the Pipeline
-
Make a code change locally.
-
Push to master:
git add . git commit -m "Updated application" git push origin master -
Watch the pipeline progress in the CodePipeline console.
-
Verify the new deployment in the ECS console.
Enhancing the Pipeline
Automated Testing
Add test commands to buildspec.yml before the Docker build step:
phases:
build:
commands:
- echo Running unit tests...
- npm test
- echo Building Docker image...
- docker build -t $REPOSITORY_URI:latest .
Infrastructure as Code
Define all AWS resources in CloudFormation or Terraform templates. Keeping infrastructure code in version control alongside application code means environment setup is reproducible and changes are auditable.
Dependency Management with CodeArtifact
AWS CodeArtifact stores and serves npm packages privately. This is useful for sharing internal libraries across services without publishing them to the public npm registry.
Security Best Practices
- Assign least-privilege IAM roles to each CodeBuild and ECS component.
- Store secrets in AWS Secrets Manager, not in environment variables or source code.
- Configure VPCs, subnets, and security groups to restrict network access.
- Enable CloudTrail and CloudWatch for audit logging.
Monitoring and Logging
- CloudWatch Logs collects output from ECS tasks.
- AWS X-Ray traces requests through the deployed application.
- Configure Amazon SNS to send notifications when pipeline stages fail.
Cost Optimization
- Use ECS Spot capacity for workloads that can tolerate interruption.
- Right-size task CPU and memory based on actual CloudWatch metrics rather than guessing.
- Review and delete unused ECR images and ECS task definitions regularly.
Conclusion
Once this pipeline is in place, deploying a change requires only a git push. CodePipeline handles the rest: building the image, running tests, pushing to ECR, and rolling the new version out to ECS. The setup cost is real, but it pays off quickly in time saved and production incidents avoided.