5. Set Up CI/CD Pipeline for ECS with AWS CodePipeline

Goal

Create an automated CI/CD pipeline that builds Docker images and deploys them to ECS whenever you push code to GitHub.

What you’ll learn:

  • How to set up GitHub integration with AWS CodeStar Connections
  • How to configure CodeBuild for multi-architecture Docker builds
  • How to create CodePipeline with Source, Build, and Deploy stages
  • Best practices for IAM roles and automated ECS deployments

Table of Contents

  1. Prerequisites
  2. Exercise Steps
  3. Common Issues
  4. Summary
  5. Done! 🎉

Prerequisites

Before starting, ensure you have:

  • ✓ AWS account with administrative access
  • ✓ GitHub repository with Dockerfile and buildspec.yml
  • ✓ ECS cluster and service already deployed
  • ✓ Basic understanding of CI/CD concepts

Exercise Steps

Overview

  1. Create ECR Repository
  2. Create GitHub Connection
  3. Create S3 Bucket for Artifacts
  4. Create IAM Roles
  5. Create CodeBuild Project
  6. Create CodePipeline
  7. Verify Pipeline

Step 1: Create ECR Repository

Create a private Docker registry to store your application images.

  1. Navigate to ECR → Repositories

  2. Click Create repository

  3. Select Private

  4. Enter repository name: my-ecs-php-app

  5. Enable “Scan on push”

  6. Enable “Tag immutability” (optional, for production)

  7. Click Create repository

  8. Note down the repository URI (e.g., 123456789.dkr.ecr.us-east-1.amazonaws.com/my-ecs-php-app)

ℹ Concept Deep Dive

ECR (Elastic Container Registry) is AWS’s private Docker registry. It integrates seamlessly with ECS and CodeBuild, providing secure image storage with vulnerability scanning.

Scan on push automatically scans images for CVEs (security vulnerabilities) when pushed.

✓ Quick check: ECR repository created with scan-on-push enabled

Step 2: Create GitHub Connection

Set up secure GitHub integration using CodeStar Connections.

  1. Navigate to Developer Tools → Connections

  2. Click Create connection

  3. Select GitHub

  4. Enter connection name: github-connection

  5. Click Connect to GitHub

  6. Click “Install a new app” (or select existing GitHub App)

  7. Authorize AWS Connector for GitHub on your GitHub account

  8. Select repositories to grant access (select your PHP app repo)

  9. Click Install

  10. Click Connect

  11. Note down the connection ARN (e.g., arn:aws:codestar-connections:...)

ℹ Concept Deep Dive

CodeStar Connections provide secure, token-based authentication with GitHub without storing credentials. The connection uses a GitHub App that AWS manages.

Connection status must be “Available” before use. If pending, complete the GitHub authorization.

âš  Common Mistakes

  • Not completing GitHub authorization - connection stays in “Pending” state
  • Not granting repository access - pipeline can’t access source code

✓ Quick check: Connection status shows “Available”

Step 3: Create S3 Bucket for Artifacts

Create an S3 bucket to store pipeline artifacts (source code and build outputs).

  1. Navigate to S3 → Buckets

  2. Click Create bucket

  3. Enter bucket name: dev-pipeline-artifacts-<your-account-id> (must be globally unique)

  4. Select your AWS region

  5. Leave “Block all public access” enabled

  6. Enable “Bucket Versioning”

  7. Click Create bucket

ℹ Concept Deep Dive

Artifact bucket stores zip files containing source code between pipeline stages. CodePipeline automatically manages uploads/downloads.

Versioning allows rollback to previous pipeline executions if needed.

✓ Quick check: S3 bucket created with versioning enabled

Step 4: Create IAM Roles

Create IAM roles for CodePipeline and CodeBuild with appropriate permissions.

Create CodePipeline Service Role

  1. Navigate to IAM → Roles → Create role

  2. Select “AWS service” → “CodePipeline”

  3. Click Next (policy is auto-attached)

  4. Enter role name: dev-codepipeline-service-role

  5. Click Create role

  6. Click on the created role

  7. Click Add permissions → Attach policies

  8. Search and attach AmazonECS_FullAccess

  9. Click Add permissions → Create inline policy

  10. Select JSON tab and paste:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:GetObjectVersion",
        "s3:PutObject",
        "s3:GetBucketLocation",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::dev-pipeline-artifacts-*",
        "arn:aws:s3:::dev-pipeline-artifacts-*/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "codebuild:BatchGetBuilds",
        "codebuild:StartBuild"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "codestar-connections:UseConnection"
      ],
      "Resource": "*"
    }
  ]
}
  1. Enter policy name: CodePipelineCustomPolicy

  2. Click Create policy

Create CodeBuild Service Role

  1. Click Create role again

  2. Select “AWS service” → “CodeBuild”

  3. Click Next

  4. Enter role name: dev-codebuild-service-role

  5. Click Create role

  6. Click on the created role

  7. Click Add permissions → Create inline policy

  8. Select JSON tab and paste:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:PutImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::dev-pipeline-artifacts-*/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    }
  ]
}
  1. Enter policy name: CodeBuildCustomPolicy

  2. Click Create policy

ℹ Concept Deep Dive

CodePipeline role needs permissions to:

  • Access S3 artifacts
  • Trigger CodeBuild builds
  • Use CodeStar connections
  • Deploy to ECS (via AmazonECS_FullAccess)

CodeBuild role needs permissions to:

  • Push/pull images from ECR
  • Access S3 artifacts
  • Write CloudWatch logs

✓ Quick check: Two roles created with inline policies attached

Step 5: Create CodeBuild Project

Create a CodeBuild project that builds multi-architecture Docker images.

  1. Navigate to CodeBuild → Build projects

  2. Click Create build project

  3. Enter project name: dev-php-app-build

  4. Under Source:

    • Source provider: AWS CodePipeline
  5. Under Environment:

    • Environment image: Managed image
    • Operating system: Ubuntu
    • Runtime: Standard
    • Image: aws/codebuild/standard:7.0
    • Image version: Always use the latest
    • Enable “Privileged” (required for Docker builds)
    • Service role: Existing service role
    • Role name: dev-codebuild-service-role
  6. Under Environment variables, add:

    • Name: AWS_ACCOUNT_ID, Value: <your-account-id>, Type: Plaintext
    • Name: AWS_DEFAULT_REGION, Value: <your-region>, Type: Plaintext
    • Name: IMAGE_REPO_NAME, Value: my-ecs-php-app, Type: Plaintext
    • Name: CONTAINER_NAME, Value: php-app-container, Type: Plaintext
  7. Under Buildspec:

    • Build specifications: Use a buildspec file
    • Buildspec name: buildspec.yml
  8. Under Artifacts:

    • Type: AWS CodePipeline
  9. Under Logs:

    • Check “CloudWatch logs”
    • Group name: /aws/codebuild/dev-php-app-build
  10. Click Create build project

ℹ Concept Deep Dive

Privileged mode is required for Docker-in-Docker builds (building Docker images inside a Docker container).

Environment variables are injected into the build environment and used by buildspec.yml to configure the build.

buildspec.yml must exist in your repository root with build instructions.

âš  Common Mistakes

  • Forgetting to enable Privileged mode - Docker builds will fail
  • Wrong environment variable names - buildspec.yml expects specific names
  • Using wrong CodeBuild image - must support Docker and buildx

✓ Quick check: Build project created with privileged mode enabled

Step 6: Create CodePipeline

Create the pipeline that orchestrates Source → Build → Deploy stages.

  1. Navigate to CodePipeline → Pipelines

  2. Click Create pipeline

  3. Enter pipeline name: dev-php-app-pipeline

  4. Under Service role:

    • Role name: Existing service role
    • Select dev-codepipeline-service-role
  5. Under Artifact store:

    • Select Custom location
    • Bucket: dev-pipeline-artifacts-<your-account-id>
  6. Click Next

Configure Source Stage

  1. Under Source provider: GitHub (Version 2)

  2. Select connection: github-connection

  3. Enter repository name: <owner>/<repo> (e.g., larsappel/my-ecs-php-app)

  4. Select branch: main

  5. Select output artifact format: CodePipeline default

  6. Click Next

Configure Build Stage

  1. Select build provider: AWS CodeBuild

  2. Select region: (your region)

  3. Select project name: dev-php-app-build

  4. Leave build type as Single build

  5. Click Next

Configure Deploy Stage

  1. Select deploy provider: Amazon ECS

  2. Select cluster name: dev-cluster (your existing cluster)

  3. Enter service name: dev-php-app-alb-service (your existing service)

  4. Enter image definitions file: imagedefinitions.json

  5. Click Next

  6. Review all settings

  7. Click Create pipeline

  8. Pipeline will automatically start - let it run

ℹ Concept Deep Dive

Pipeline stages:

  • Source: Pulls code from GitHub when commits are pushed
  • Build: Runs CodeBuild to create Docker image and push to ECR
  • Deploy: Updates ECS service with new image from imagedefinitions.json

imagedefinitions.json is generated by buildspec.yml and tells ECS which container to update with which image.

Automatic triggers: Pipeline runs automatically on every push to the configured branch.

âš  Common Mistakes

  • Wrong cluster or service name - deploy will fail
  • Image definitions file name typo - must be exactly imagedefinitions.json
  • Forgetting to push buildspec.yml to repo - build will fail

✓ Quick check: Pipeline created and first execution started

Step 7: Verify Pipeline

Verify that the pipeline successfully builds and deploys your application.

Check Pipeline Execution

  1. Navigate to CodePipeline → Pipelines → dev-php-app-pipeline

  2. Monitor the execution progress:

    • Source stage should succeed quickly (pulling from GitHub)
    • Build stage takes 3-5 minutes (building Docker images)
    • Deploy stage takes 1-2 minutes (updating ECS service)
  3. If any stage fails, click Details to see error messages

Check Build Logs

  1. Click Details on Build stage

  2. Click Link to execution details

  3. Click Build logs

  4. Verify Docker build completed and images pushed to ECR

Check ECR Images

  1. Navigate to ECR → Repositories → my-ecs-php-app

  2. Verify images with tags: commit hash, latest, and build number

Check ECS Deployment

  1. Navigate to ECS → Clusters → dev-cluster → Services → dev-php-app-alb-service

  2. Click Deployments tab

  3. Verify new deployment is running

  4. Click Tasks tab

  5. Verify new tasks are using the new image

Test Application

  1. Navigate to EC2 → Load Balancers → dev-php-app-alb

  2. Copy DNS name

  3. Open browser and navigate to http://<alb-dns>

  4. Verify application is running with latest code

ℹ Concept Deep Dive

Pipeline execution ID uniquely identifies each run. You can view history of all executions.

Zero-downtime deployment: ECS automatically starts new tasks, waits for health checks, then stops old tasks.

Rollback: If deployment fails, ECS keeps old tasks running and circuit breaker can automatically roll back.

âš  Common Mistakes

  • Not waiting for health checks - deployment appears to fail but is actually pending
  • Checking old tasks - new tasks take 2-3 minutes to become healthy
  • Testing before deployment completes - ALB might route to old tasks

✓ Success indicators:

  • ✓ All pipeline stages show “Succeeded”
  • ✓ New images in ECR with multiple tags
  • ✓ ECS service deployment completed
  • ✓ New tasks running with new image
  • ✓ Application accessible via ALB

Common Issues

“Source stage fails with connection error”:

  • Verify CodeStar connection status is “Available”
  • Check GitHub App has access to the repository
  • Ensure repository name format is correct (owner/repo)

“Build stage fails with Docker permission denied”:

  • Verify CodeBuild project has Privileged mode enabled
  • Check CodeBuild role has ECR permissions
  • Ensure buildspec.yml exists in repository root

“Build fails with ‘Commands[X] subkeys error’”:

  • Check buildspec.yml for YAML syntax errors
  • Avoid colons (:) in echo statements - use hyphens (-) instead
  • Ensure proper indentation in buildspec.yml

“Deploy stage fails with permission error”:

  • Verify CodePipeline role has ECS permissions (AmazonECS_FullAccess)
  • Check cluster and service names match exactly
  • Ensure ECS service exists and is active

“Deployment succeeds but old image still running”:

  • Check imagedefinitions.json container name matches task definition
  • Verify ECS service is actually updating (check Deployments tab)
  • Wait for health checks to pass (can take 2-3 minutes)

Summary

You’ve successfully created an automated CI/CD pipeline that:

Key takeaway:

AWS-native CI/CD with CodePipeline, CodeBuild, and ECR provides fully managed continuous deployment with automatic triggers, artifact management, and ECS integration. This is production-ready infrastructure for containerized applications.

Done! 🎉

You’ve learned how to set up a complete CI/CD pipeline for ECS applications. Now every push to your GitHub repository automatically builds and deploys to your running ECS service!

To trigger a deployment:

  1. Make a code change in your repository
  2. Commit and push to the main branch
  3. Watch the pipeline automatically execute
  4. Verify deployment in ECS

To clean up:

  1. Delete CodePipeline
  2. Delete CodeBuild project
  3. Delete ECR repository (and images)
  4. Delete S3 bucket
  5. Delete CodeStar connection
  6. Delete IAM roles