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:

  1. Source: Code is pushed to a version control system (AWS CodeCommit or GitHub).
  2. Build: CodeBuild builds the Docker image and runs tests.
  3. 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.

  1. Navigate to the AWS CodeCommit console.

  2. Create a new repository named my-app-repo.

  3. Clone the repository to your local machine:

    git clone https://git-codecommit.us-east-1.amazonaws.com/v1/repos/my-app-repo
  4. 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.

  1. Navigate to the Amazon ECR console.
  2. Create a new repository named my-app-repo.
  3. Note the repository URI for use in buildspec.yml.

Step 5: Configuring AWS CodeBuild

  1. Navigate to the AWS CodeBuild console.
  2. Create a new build project named my-app-build.
  3. 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

  1. Navigate to the AWS CodePipeline console.
  2. Create a new pipeline named my-app-pipeline.
  3. Source Stage: AWS CodeCommit, my-app-repo, master branch.
  4. Build Stage: AWS CodeBuild, my-app-build project.
  5. Deploy Stage: AWS ECS, my-ecs-cluster, my-app-service.

Step 7: Configuring Amazon ECS

Create an ECS Cluster

  1. Navigate to the Amazon ECS console.
  2. Create a cluster named my-ecs-cluster. Use Fargate if you want to avoid managing EC2 instances.

Define a Task Definition

  1. Go to Task Definitions.
  2. Create my-app-task with:
    • Task Role: IAM role with necessary permissions.
    • Network Mode: awsvpc for Fargate.
    • Container: name my-app-container, image from ECR, port 3000 mapped to host 80.

Create a Service

  1. Go to Services and create my-app-service.
  2. Launch type: Fargate. Task definition: my-app-task. Enable rolling updates.

Step 8: Testing the Pipeline

  1. Make a code change locally.

  2. Push to master:

    git add .
    git commit -m "Updated application"
    git push origin master
  3. Watch the pipeline progress in the CodePipeline console.

  4. 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.