All posts

AWS DevOps Tools: CodePipeline, CodeBuild, and ECS

· Pavan Y N · aws, devops, codepipeline, ecs, automation

Published: October 30, 2025
Reading Time: 23 minutes
Tags: AWS, DevOps, CodePipeline, CodeBuild, ECS, Automation

# Introduction

AWS provides a comprehensive suite of DevOps tools that integrate seamlessly with the AWS ecosystem. This guide covers AWS CodePipeline, CodeBuild, CodeDeploy, ECS, ECR, and how to build complete CI/CD workflows on AWS.

# AWS CodePipeline

## What is CodePipeline?

AWS CodePipeline is a fully managed continuous delivery service that automates release pipelines for fast and reliable application updates.

## Basic Pipeline Structure

code
# pipeline.yml (CloudFormation)
AWSTemplateFormatVersion: '2010-09-09'
Description: 'CI/CD Pipeline for Application'

Resources:
  ArtifactBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${AWS::StackName}-artifacts'
      VersioningConfiguration:
        Status: Enabled
      LifecycleConfiguration:
        Rules:
          - Id: DeleteOldArtifacts
            Status: Enabled
            ExpirationInDays: 30

  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Sub '${AWS::StackName}-pipeline'
      RoleArn: !GetAtt PipelineRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              ActionTypeId:
                Category: Source
                Owner: ThirdParty
                Provider: GitHub
                Version: '1'
              Configuration:
                Owner: !Ref GitHubOwner
                Repo: !Ref GitHubRepo
                Branch: !Ref GitHubBranch
                OAuthToken: !Ref GitHubToken
              OutputArtifacts:
                - Name: SourceOutput
        
        - Name: Build
          Actions:
            - Name: BuildAction
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: '1'
              Configuration:
                ProjectName: !Ref CodeBuildProject
              InputArtifacts:
                - Name: SourceOutput
              OutputArtifacts:
                - Name: BuildOutput
        
        - Name: Deploy
          Actions:
            - Name: DeployAction
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: ECS
                Version: '1'
              Configuration:
                ClusterName: !Ref ECSCluster
                ServiceName: !Ref ECSService
                FileName: imagedefinitions.json
              InputArtifacts:
                - Name: BuildOutput

  PipelineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
            Action: 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AWSCodePipelineFullAccess'
      Policies:
        - PolicyName: PipelinePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - 's3:*'
                  - 'codebuild:*'
                  - 'ecs:*'
                Resource: '*'

# AWS CodeBuild

## BuildSpec Configuration

code
# buildspec.yml
version: 0.2

env:
  variables:
    NODE_ENV: production
  parameter-store:
    DATABASE_URL: /myapp/database-url
    API_KEY: /myapp/api-key
  secrets-manager:
    DOCKER_PASSWORD: prod/docker:password

phases:
  install:
    runtime-versions:
      nodejs: 20
    commands:
      - echo "Installing dependencies..."
      - npm ci
  
  pre_build:
    commands:
      - echo "Running pre-build tasks..."
      - npm run lint
      - npm audit --audit-level=high
      - echo "Logging in to Amazon ECR..."
      - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
  
  build:
    commands:
      - echo "Building application..."
      - npm run build
      - echo "Building Docker image..."
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  
  post_build:
    commands:
      - echo "Running tests..."
      - npm test
      - echo "Pushing Docker image..."
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - echo "Creating imagedefinitions.json..."
      - printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imagedefinitions.json

artifacts:
  files:
    - imagedefinitions.json
    - appspec.yml
    - taskdef.json

cache:
  paths:
    - 'node_modules/**/*'
    - '.npm/**/*'

reports:
  test-results:
    files:
      - 'test-results/**/*'
    file-format: JunitXml
  
  coverage-report:
    files:
      - 'coverage/clover.xml'
    file-format: CoberturaXml

## CodeBuild Project (CloudFormation)

code
CodeBuildProject:
  Type: AWS::CodeBuild::Project
  Properties:
    Name: !Sub '${AWS::StackName}-build'
    ServiceRole: !GetAtt CodeBuildRole.Arn
    Artifacts:
      Type: CODEPIPELINE
    Environment:
      Type: LINUX_CONTAINER
      ComputeType: BUILD_GENERAL1_MEDIUM
      Image: aws/codebuild/standard:7.0
      PrivilegedMode: true
      EnvironmentVariables:
        - Name: AWS_ACCOUNT_ID
          Value: !Ref AWS::AccountId
        - Name: IMAGE_REPO_NAME
          Value: !Ref ECRRepository
        - Name: IMAGE_TAG
          Value: latest
        - Name: CONTAINER_NAME
          Value: myapp
    Source:
      Type: CODEPIPELINE
      BuildSpec: buildspec.yml
    Cache:
      Type: S3
      Location: !Sub '${ArtifactBucket}/build-cache'
    LogsConfig:
      CloudWatchLogs:
        Status: ENABLED
        GroupName: !Sub '/aws/codebuild/${AWS::StackName}'

# Amazon ECS (Elastic Container Service)

## Task Definition

code
{
  "family": "myapp-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::123456789012:role/ecsTaskRole",
  "containerDefinitions": [
    {
      "name": "myapp",
      "image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 3000,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "NODE_ENV",
          "value": "production"
        },
        {
          "name": "PORT",
          "value": "3000"
        }
      ],
      "secrets": [
        {
          "name": "DATABASE_URL",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/database-url"
        },
        {
          "name": "API_KEY",
          "valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/api-key"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/myapp",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}

## ECS Service (CloudFormation)

code
ECSCluster:
  Type: AWS::ECS::Cluster
  Properties:
    ClusterName: !Sub '${AWS::StackName}-cluster'
    CapacityProviders:
      - FARGATE
      - FARGATE_SPOT
    DefaultCapacityProviderStrategy:
      - CapacityProvider: FARGATE
        Weight: 1
      - CapacityProvider: FARGATE_SPOT
        Weight: 4

ECSService:
  Type: AWS::ECS::Service
  DependsOn: LoadBalancerListener
  Properties:
    ServiceName: !Sub '${AWS::StackName}-service'
    Cluster: !Ref ECSCluster
    TaskDefinition: !Ref TaskDefinition
    DesiredCount: 3
    LaunchType: FARGATE
    NetworkConfiguration:
      AwsvpcConfiguration:
        AssignPublicIp: DISABLED
        SecurityGroups:
          - !Ref ECSSecurityGroup
        Subnets:
          - !Ref PrivateSubnet1
          - !Ref PrivateSubnet2
    LoadBalancers:
      - ContainerName: myapp
        ContainerPort: 3000
        TargetGroupArn: !Ref TargetGroup
    HealthCheckGracePeriodSeconds: 60
    DeploymentConfiguration:
      MaximumPercent: 200
      MinimumHealthyPercent: 100
      DeploymentCircuitBreaker:
        Enable: true
        Rollback: true

ServiceAutoScaling:
  Type: AWS::ApplicationAutoScaling::ScalableTarget
  Properties:
    MaxCapacity: 10
    MinCapacity: 2
    ResourceId: !Sub 'service/${ECSCluster}/${ECSService.Name}'
    RoleARN: !GetAtt AutoScalingRole.Arn
    ScalableDimension: ecs:service:DesiredCount
    ServiceNamespace: ecs

ServiceScalingPolicy:
  Type: AWS::ApplicationAutoScaling::ScalingPolicy
  Properties:
    PolicyName: !Sub '${AWS::StackName}-scaling-policy'
    PolicyType: TargetTrackingScaling
    ScalingTargetId: !Ref ServiceAutoScaling
    TargetTrackingScalingPolicyConfiguration:
      PredefinedMetricSpecification:
        PredefinedMetricType: ECSServiceAverageCPUUtilization
      TargetValue: 70.0
      ScaleInCooldown: 300
      ScaleOutCooldown: 60

# Complete CI/CD Pipeline Example

## Terraform Configuration

code
# main.tf
provider "aws" {
  region = var.aws_region
}

# ECR Repository
resource "aws_ecr_repository" "app" {
  name                 = var.app_name
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

  encryption_configuration {
    encryption_type = "KMS"
    kms_key         = aws_kms_key.ecr.arn
  }
}

# CodeBuild Project
resource "aws_codebuild_project" "app" {
  name          = "${var.app_name}-build"
  service_role  = aws_iam_role.codebuild.arn
  build_timeout = 30

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    compute_type                = "BUILD_GENERAL1_MEDIUM"
    image                       = "aws/codebuild/standard:7.0"
    type                        = "LINUX_CONTAINER"
    privileged_mode             = true
    image_pull_credentials_type = "CODEBUILD"

    environment_variable {
      name  = "AWS_ACCOUNT_ID"
      value = data.aws_caller_identity.current.account_id
    }

    environment_variable {
      name  = "IMAGE_REPO_NAME"
      value = aws_ecr_repository.app.name
    }

    environment_variable {
      name  = "IMAGE_TAG"
      value = "latest"
    }
  }

  source {
    type      = "CODEPIPELINE"
    buildspec = file("${path.module}/buildspec.yml")
  }

  cache {
    type     = "S3"
    location = "${aws_s3_bucket.artifacts.bucket}/build-cache"
  }

  logs_config {
    cloudwatch_logs {
      group_name = "/aws/codebuild/${var.app_name}"
    }
  }
}

# CodePipeline
resource "aws_codepipeline" "app" {
  name     = "${var.app_name}-pipeline"
  role_arn = aws_iam_role.codepipeline.arn

  artifact_store {
    location = aws_s3_bucket.artifacts.bucket
    type     = "S3"
  }

  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "ThirdParty"
      provider         = "GitHub"
      version          = "1"
      output_artifacts = ["source_output"]

      configuration = {
        Owner      = var.github_owner
        Repo       = var.github_repo
        Branch     = var.github_branch
        OAuthToken = var.github_token
      }
    }
  }

  stage {
    name = "Build"

    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      version          = "1"
      input_artifacts  = ["source_output"]
      output_artifacts = ["build_output"]

      configuration = {
        ProjectName = aws_codebuild_project.app.name
      }
    }
  }

  stage {
    name = "Deploy"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "ECS"
      version         = "1"
      input_artifacts = ["build_output"]

      configuration = {
        ClusterName = aws_ecs_cluster.main.name
        ServiceName = aws_ecs_service.app.name
        FileName    = "imagedefinitions.json"
      }
    }
  }
}

# ECS Cluster
resource "aws_ecs_cluster" "main" {
  name = "${var.app_name}-cluster"

  setting {
    name  = "containerInsights"
    value = "enabled"
  }
}

# ECS Task Definition
resource "aws_ecs_task_definition" "app" {
  family                   = "${var.app_name}-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  cpu                      = "512"
  memory                   = "1024"
  execution_role_arn       = aws_iam_role.ecs_execution.arn
  task_role_arn            = aws_iam_role.ecs_task.arn

  container_definitions = jsonencode([
    {
      name      = var.app_name
      image     = "${aws_ecr_repository.app.repository_url}:latest"
      essential = true

      portMappings = [
        {
          containerPort = 3000
          protocol      = "tcp"
        }
      ]

      environment = [
        {
          name  = "NODE_ENV"
          value = "production"
        }
      ]

      secrets = [
        {
          name      = "DATABASE_URL"
          valueFrom = aws_secretsmanager_secret.database_url.arn
        }
      ]

      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = aws_cloudwatch_log_group.app.name
          "awslogs-region"        = var.aws_region
          "awslogs-stream-prefix" = "ecs"
        }
      }

      healthCheck = {
        command     = ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"]
        interval    = 30
        timeout     = 5
        retries     = 3
        startPeriod = 60
      }
    }
  ])
}

# ECS Service
resource "aws_ecs_service" "app" {
  name            = "${var.app_name}-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.app.arn
  desired_count   = 3
  launch_type     = "FARGATE"

  network_configuration {
    security_groups  = [aws_security_group.ecs_tasks.id]
    subnets          = aws_subnet.private[*].id
    assign_public_ip = false
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.app.arn
    container_name   = var.app_name
    container_port   = 3000
  }

  deployment_configuration {
    maximum_percent         = 200
    minimum_healthy_percent = 100

    deployment_circuit_breaker {
      enable   = true
      rollback = true
    }
  }

  depends_on = [aws_lb_listener.app]
}

# STAR Approach: AWS DevOps Implementation

Situation: Company using on-premises Jenkins with manual deployments to EC2, experiencing slow releases and frequent failures.

Task: Migrate to AWS-native CI/CD using CodePipeline, CodeBuild, and ECS for automated, reliable deployments.

Action:

  1. •

    Infrastructure Setup:

    • →Created VPC with public/private subnets
    • →Set up ECS Fargate cluster
    • →Configured Application Load Balancer
    • →Implemented auto-scaling policies
  2. •

    CI/CD Pipeline:

    • →Built CodePipeline with GitHub integration
    • →Configured CodeBuild for Docker builds
    • →Set up ECR for container registry
    • →Implemented blue-green deployments
  3. •

    Security Implementation:

    • →Used Secrets Manager for credentials
    • →Implemented IAM roles with least privilege
    • →Enabled ECR image scanning
    • →Configured VPC endpoints for private communication
  4. •

    Monitoring & Logging:

    • →Enabled Container Insights
    • →Set up CloudWatch dashboards
    • →Configured alarms for critical metrics
    • →Implemented X-Ray for distributed tracing
  5. •

    Cost Optimization:

    • →Used Fargate Spot for non-production
    • →Implemented auto-scaling
    • →Set up lifecycle policies for ECR
    • →Optimized task resource allocation

Result:

  • →Deployment time reduced from 2 hours to 10 minutes
  • →Deployment success rate improved from 70% to 98%
  • →Infrastructure costs reduced by 35%
  • →Zero-downtime deployments achieved
  • →Mean time to recovery decreased by 80%
  • →Developer productivity increased by 50%

# Best Practices

## 1. Pipeline Design

  • →Use separate pipelines for different environments
  • →Implement approval gates for production
  • →Enable pipeline notifications
  • →Version control all configurations

## 2. Security

  • →Use Secrets Manager for sensitive data
  • →Enable ECR image scanning
  • →Implement least privilege IAM roles
  • →Use VPC endpoints for private communication

## 3. Performance

  • →Enable build caching
  • →Use appropriate compute types
  • →Implement parallel stages
  • →Optimize Docker images

## 4. Monitoring

  • →Enable Container Insights
  • →Set up CloudWatch alarms
  • →Implement distributed tracing
  • →Monitor pipeline metrics

## 5. Cost Optimization

  • →Use Fargate Spot when possible
  • →Implement auto-scaling
  • →Set up lifecycle policies
  • →Right-size task resources

# Conclusion

AWS DevOps tools provide a comprehensive, fully managed solution for CI/CD workflows. By leveraging CodePipeline, CodeBuild, and ECS, teams can build scalable, secure, and cost-effective deployment pipelines that integrate seamlessly with the AWS ecosystem.

# Additional Resources


Ready to build on AWS? Start with a simple pipeline and gradually add more sophisticated features!