---
title: "AWS CodePipeline and CodeBuild: Pipeline Structure, ECR Integration, ECS/EKS Deployments, and Cross-Account Patterns"
description: "Reference for AWS CodePipeline and CodeBuild covering pipeline structure, buildspec.yml, source/build/deploy stages, ECR integration, ECS and EKS deployment actions, cross-account deployments, and EventBridge triggers."
url: https://agent-zone.ai/knowledge/cicd/aws-codepipeline-patterns/
section: knowledge
date: 2026-02-22
categories: ["cicd"]
tags: ["aws","codepipeline","codebuild","ecr","ecs","eks","cicd","cross-account","eventbridge","buildspec"]
skills: ["ci-pipeline-design","aws-cicd-authoring","aws-deployment-patterns","cross-account-iam"]
tools: ["aws-codepipeline","aws-codebuild","aws-ecr","aws-ecs","aws-eks","aws-cli","docker"]
levels: ["intermediate","advanced"]
word_count: 1345
formats:
  json: https://agent-zone.ai/knowledge/cicd/aws-codepipeline-patterns/index.json
  html: https://agent-zone.ai/knowledge/cicd/aws-codepipeline-patterns/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=AWS+CodePipeline+and+CodeBuild%3A+Pipeline+Structure%2C+ECR+Integration%2C+ECS%2FEKS+Deployments%2C+and+Cross-Account+Patterns
---


# AWS CodePipeline and CodeBuild

AWS CodePipeline orchestrates CI/CD workflows as a series of stages. CodeBuild executes the actual build and test commands. Together they provide a fully managed pipeline that integrates natively with S3, ECR, ECS, EKS, Lambda, and CloudFormation. No servers to manage, no agents to maintain -- but the trade-off is less flexibility than self-hosted systems and tighter coupling to the AWS ecosystem.

## Pipeline Structure

A CodePipeline has stages, and each stage has actions. Actions can run in parallel or sequentially within a stage. The most common pattern is Source -> Build -> Deploy:

```json
{
  "pipeline": {
    "name": "myapp-pipeline",
    "roleArn": "arn:aws:iam::123456789012:role/codepipeline-role",
    "stages": [
      {
        "name": "Source",
        "actions": [{
          "name": "GitHubSource",
          "actionTypeId": {
            "category": "Source",
            "owner": "ThirdParty",
            "provider": "GitHub",
            "version": "1"
          },
          "configuration": {
            "Owner": "myorg",
            "Repo": "myapp",
            "Branch": "main",
            "OAuthToken": "{{resolve:secretsmanager:github-token}}"
          },
          "outputArtifacts": [{"name": "SourceOutput"}]
        }]
      },
      {
        "name": "Build",
        "actions": [{
          "name": "CodeBuild",
          "actionTypeId": {
            "category": "Build",
            "owner": "AWS",
            "provider": "CodeBuild",
            "version": "1"
          },
          "inputArtifacts": [{"name": "SourceOutput"}],
          "outputArtifacts": [{"name": "BuildOutput"}],
          "configuration": {
            "ProjectName": "myapp-build"
          }
        }]
      },
      {
        "name": "Deploy",
        "actions": [{
          "name": "ECSDeploy",
          "actionTypeId": {
            "category": "Deploy",
            "owner": "AWS",
            "provider": "ECS",
            "version": "1"
          },
          "inputArtifacts": [{"name": "BuildOutput"}],
          "configuration": {
            "ClusterName": "production",
            "ServiceName": "myapp",
            "FileName": "imagedefinitions.json"
          }
        }]
      }
    ]
  }
}
```

Most teams define pipelines with CloudFormation or Terraform rather than raw JSON. The JSON above illustrates the structure -- stages are ordered, actions within a stage can run in parallel by assigning different `runOrder` values.

In CloudFormation:

```yaml
Resources:
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: myapp-pipeline
      RoleArn: !GetAtt PipelineRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: Source
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeStarSourceConnection
                Version: '1'
              Configuration:
                ConnectionArn: !Ref GitHubConnection
                FullRepositoryId: myorg/myapp
                BranchName: main
              OutputArtifacts:
                - Name: SourceOutput
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: '1'
              Configuration:
                ProjectName: !Ref BuildProject
              InputArtifacts:
                - Name: SourceOutput
              OutputArtifacts:
                - Name: BuildOutput
```

Use CodeStar Connections (the `CodeStarSourceConnection` provider) instead of the legacy `GitHub` provider. CodeStar Connections use an AWS-managed GitHub App and do not require storing OAuth tokens.

## buildspec.yml

CodeBuild uses `buildspec.yml` to define what happens during a build. It has four phases: `install`, `pre_build`, `build`, and `post_build`:

```yaml
version: 0.2

env:
  variables:
    APP_NAME: myapp
    GO_VERSION: "1.22"
  parameter-store:
    DOCKER_PASSWORD: /codebuild/docker-password
  secrets-manager:
    API_KEY: prod/myapp:api_key

phases:
  install:
    runtime-versions:
      golang: 1.22
    commands:
      - echo "Install phase"

  pre_build:
    commands:
      - echo "Logging in to 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
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}

  build:
    commands:
      - echo "Building Docker image..."
      - docker build -t $REPOSITORY_URI:$IMAGE_TAG .
      - docker tag $REPOSITORY_URI:$IMAGE_TAG $REPOSITORY_URI:latest

  post_build:
    commands:
      - echo "Pushing to ECR..."
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - docker push $REPOSITORY_URI:latest
      - printf '[{"name":"myapp","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json

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

cache:
  paths:
    - '/root/.cache/go-build/**/*'
    - '/go/pkg/mod/**/*'
```

The `env` section pulls secrets from SSM Parameter Store and Secrets Manager at build time without embedding them in the buildspec. The `artifacts` section defines files passed to the next pipeline stage. The `cache` section persists directories to S3 between builds.

The `imagedefinitions.json` file is critical for ECS deployments. It maps container names to image URIs and tells the ECS deploy action which image to use.

## ECR Integration

The standard pattern is: CodeBuild builds the image, pushes to ECR, and outputs the image URI for the deploy stage.

CodeBuild needs IAM permissions for ECR:

```yaml
# IAM policy for CodeBuild role
- Effect: Allow
  Action:
    - ecr:GetAuthorizationToken
  Resource: '*'
- Effect: Allow
  Action:
    - ecr:BatchCheckLayerAvailability
    - ecr:GetDownloadUrlForLayer
    - ecr:BatchGetImage
    - ecr:PutImage
    - ecr:InitiateLayerUpload
    - ecr:UploadLayerPart
    - ecr:CompleteLayerUpload
  Resource: !Sub 'arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/myapp'
```

Enable Docker layer caching in CodeBuild by using the `LOCAL_DOCKER_LAYER_CACHE` or `LOCAL_CUSTOM_CACHE` modes:

```yaml
BuildProject:
  Type: AWS::CodeBuild::Project
  Properties:
    Cache:
      Type: LOCAL
      Modes:
        - LOCAL_DOCKER_LAYER_CACHE
        - LOCAL_SOURCE_CACHE
    Environment:
      Type: LINUX_CONTAINER
      ComputeType: BUILD_GENERAL1_MEDIUM
      Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
      PrivilegedMode: true
```

`PrivilegedMode: true` is required for Docker builds. Without it, the Docker daemon cannot start inside the CodeBuild container.

## ECS Deployment Actions

For ECS deployments, CodePipeline supports two action types:

**Standard ECS deploy** (`ECS` provider): Updates the service with a new task definition. Simple but performs in-place replacement -- all tasks are stopped and restarted. Use `imagedefinitions.json` to specify the new image.

**Blue/Green ECS deploy** (`CodeDeployToECS` provider): Uses CodeDeploy to perform blue/green or canary deployments. Requires an `appspec.yaml` and `taskdef.json`:

```yaml
# appspec.yaml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: myapp
          ContainerPort: 8080
```

```json
// taskdef.json
{
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "containerDefinitions": [
    {
      "name": "myapp",
      "image": "<IMAGE1_NAME>",
      "portMappings": [{"containerPort": 8080, "protocol": "tcp"}],
      "essential": true
    }
  ],
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "cpu": "256",
  "memory": "512",
  "family": "myapp"
}
```

CodePipeline replaces `<TASK_DEFINITION>` and `<IMAGE1_NAME>` placeholders with the actual values during deployment. The blue/green strategy routes traffic to the new task set via a target group swap on the ALB, with configurable rollback triggers.

## EKS Deployment

CodePipeline does not have a native EKS deploy action. The standard approach uses CodeBuild as the deploy step with `kubectl` or `helm`:

```yaml
# buildspec-deploy.yml
version: 0.2

phases:
  install:
    commands:
      - curl -LO "https://dl.k8s.io/release/v1.29.0/bin/linux/amd64/kubectl"
      - chmod +x kubectl && mv kubectl /usr/local/bin/
      - curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

  pre_build:
    commands:
      - aws eks update-kubeconfig --name $EKS_CLUSTER_NAME --region $AWS_DEFAULT_REGION

  build:
    commands:
      - |
        helm upgrade --install myapp ./chart \
          --namespace myapp \
          --set image.repository=$REPOSITORY_URI \
          --set image.tag=$IMAGE_TAG \
          --wait --timeout 5m
```

The CodeBuild role needs `eks:DescribeCluster` permission and must be mapped in the EKS cluster's `aws-auth` ConfigMap or EKS access entries:

```bash
eksctl create iamidentitymapping \
  --cluster production \
  --arn arn:aws:iam::123456789012:role/codebuild-deploy-role \
  --group system:masters \
  --username codebuild-deploy
```

Use a dedicated IAM role for EKS deploys with least-privilege Kubernetes RBAC rather than `system:masters` in production.

## Cross-Account Deployments

Production workloads typically run in a separate AWS account. CodePipeline supports cross-account actions through IAM role assumption:

```yaml
# In the pipeline account (Dev)
DeployAction:
  ActionTypeId:
    Category: Deploy
    Owner: AWS
    Provider: CloudFormation
    Version: '1'
  Configuration:
    ActionMode: CREATE_UPDATE
    StackName: myapp-stack
    TemplatePath: BuildOutput::template.yaml
    RoleArn: arn:aws:iam::999888777666:role/cloudformation-deploy-role
  RoleArn: arn:aws:iam::999888777666:role/codepipeline-cross-account-role
```

The setup requires:

1. **Pipeline account**: CodePipeline role with `sts:AssumeRole` permission for the target account role.
2. **Target account**: A role that trusts the pipeline account and has permissions to deploy resources.
3. **S3 artifact bucket**: Must have a bucket policy allowing the target account to read artifacts. Use a KMS key shared across accounts for artifact encryption.

```json
{
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::999888777666:role/codepipeline-cross-account-role"
  },
  "Action": ["s3:GetObject", "s3:GetObjectVersion"],
  "Resource": "arn:aws:s3:::pipeline-artifacts/*"
}
```

For ECR cross-account access, add a repository policy in the source account allowing the target account to pull images:

```json
{
  "Effect": "Allow",
  "Principal": {
    "AWS": "arn:aws:iam::999888777666:root"
  },
  "Action": [
    "ecr:GetDownloadUrlForLayer",
    "ecr:BatchGetImage",
    "ecr:BatchCheckLayerAvailability"
  ]
}
```

## EventBridge Triggers

CodePipeline V2 pipelines use EventBridge for source triggers instead of polling. This is faster (near-instant vs 1-minute polling) and cheaper:

```yaml
PipelineTrigger:
  Type: AWS::Events::Rule
  Properties:
    EventPattern:
      source:
        - aws.codecommit
      detail-type:
        - CodeCommit Repository State Change
      detail:
        event:
          - referenceCreated
          - referenceUpdated
        referenceType:
          - branch
        referenceName:
          - main
    Targets:
      - Arn: !Sub 'arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}'
        RoleArn: !GetAtt EventBridgeRole.Arn
        Id: CodePipelineTarget
```

For GitHub sources via CodeStar Connections, EventBridge triggers are configured automatically when you create a V2 pipeline. For ECR image push triggers:

```yaml
EventPattern:
  source:
    - aws.ecr
  detail-type:
    - ECR Image Action
  detail:
    action-type:
      - PUSH
    repository-name:
      - myapp
    image-tag:
      - latest
```

This enables chained pipelines: one pipeline builds and pushes an image to ECR, and the EventBridge rule triggers a second pipeline that deploys it. This decoupling is useful when build and deploy responsibilities belong to different teams.

## Common Mistakes

1. **Forgetting `PrivilegedMode` for Docker builds.** CodeBuild cannot run Docker commands without privileged mode. The error is `Cannot connect to the Docker daemon` and it has nothing to do with Docker installation.
2. **Using the legacy GitHub source provider.** The V1 `GitHub` provider stores OAuth tokens. CodeStar Connections use a managed GitHub App with better security and no token rotation burden.
3. **Not encrypting the artifact bucket with a shared KMS key.** Cross-account pipelines fail silently when the target account cannot decrypt artifacts. Always use a customer-managed KMS key with cross-account grants.
4. **Hardcoding account IDs in buildspec.yml.** Use CodeBuild environment variables (`$AWS_ACCOUNT_ID`, `$AWS_DEFAULT_REGION`) or pass them as CodePipeline variable overrides. Hardcoded IDs break when moving pipelines between accounts.
5. **Skipping `--wait` in Helm deploy steps.** Without `--wait`, CodeBuild reports success as soon as `helm upgrade` returns, before pods are actually running. The deploy might fail silently while the pipeline shows green.

