AWS CodeBuild


1. Important Points#

AWS CodeBuild 是 managed build service。它适合把 build/test/package/docker build 这类动作放到 AWS 托管环境里跑,并和 S3、ECR、CloudWatch Logs、CodePipeline 集成。

CodeBuild 用来做:
    compile / test / package
    build Docker image
    push image to ECR
    run CI job from CLI / webhook / CodePipeline
    produce build artifacts

CodeBuild 不负责:
    long-running service runtime
    deployment orchestration by itself
    source code hosting
    replacing buildspec discipline

核心原则:

project:
    defines build environment, service role, logs, timeout, source/artifact defaults

build:
    one execution of a project
    can override source, buildspec, env vars, image, timeout

buildspec.yml:
    should live at source root
    declares install/pre_build/build/post_build commands

Docker build:
    requires privileged mode for Docker-in-Docker style build
    service role needs ECR permissions

2. Core Concepts#

Concept Meaning Production Note
Project reusable build definition one per app or build pattern
Build one execution immutable build ID
Source Git/S3/CodePipeline input script can override to S3 zip
Buildspec command file keep in repo root
Environment build container image and compute Docker build needs privileged mode
Service role IAM role assumed by CodeBuild grants S3/ECR/Logs access
Env override per-build variables useful for tag, image name, deploy env
Logs CloudWatch Logs / S3 keep for debugging

3. IAM#

deploy caller#

这个 principal 是运行 shell 脚本的人或 CI system。它负责上传 source zip、启动 CodeBuild、轮询结果、检查 ECR image。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-codebuild-source-bucket/codebuild-sources/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "codebuild:StartBuild",
        "codebuild:BatchGetBuilds"
      ],
      "Resource": "arn:aws:codebuild:ap-east-1:111122223333:project/order-api-build"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:DescribeImages"
      ],
      "Resource": "arn:aws:ecr:ap-east-1:111122223333:repository/order-api"
    }
  ]
}

CodeBuild service role#

这个 role 是 CodeBuild project 使用的 service role。它需要读 source zip、写 CloudWatch Logs、推 ECR image。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-codebuild-source-bucket/codebuild-sources/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:GetAuthorizationToken"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload",
        "ecr:PutImage",
        "ecr:BatchGetImage",
        "ecr:GetDownloadUrlForLayer"
      ],
      "Resource": "arn:aws:ecr:ap-east-1:111122223333:repository/order-api"
    }
  ]
}

4. Project Configuration#

CLI 创建项目示例。这里项目 source 可以先用 S3 占位;真正构建时由脚本通过 --source-type-override S3--source-location-override 指定 zip。

aws codebuild create-project \
  --name order-api-build \
  --source type=S3,location=my-codebuild-source-bucket/codebuild-sources/placeholder.zip \
  --artifacts type=NO_ARTIFACTS \
  --environment type=LINUX_CONTAINER,image=aws/codebuild/standard:7.0,computeType=BUILD_GENERAL1_SMALL,privilegedMode=true \
  --service-role arn:aws:iam::111122223333:role/codebuild-order-api-role \
  --logs-config cloudWatchLogs='{status=ENABLED,groupName=/aws/codebuild/order-api-build}' \
  --timeout-in-minutes 30 \
  --region ap-east-1

Notes:

privilegedMode=true:
    required when buildspec runs docker build / docker push

source zip:
    must contain buildspec.yml at archive root
    avoid zipping the parent directory itself

logs:
    CloudWatch Logs is the first place to check failed builds

5. buildspec.yml For Docker Image#

Source zip 里需要包含 buildspec.yml。下面示例使用外部脚本传进来的 env vars:AWS_ACCOUNT_IDAWS_REGIONECR_REPOSITORYIMAGE_TAG

如果 Dockerfile 默认写的是 Docker Hub 镜像:

FROM node:22-alpine

CodeBuild 里经常会遇到 Docker Hub anonymous pull rate limit:

toomanyrequests:
    You have reached your unauthenticated pull rate limit.

解决方式是先把 node:22-alpine 同步到自己的 ECR,例如:

111122223333.dkr.ecr.ap-east-1.amazonaws.com/base/node:22-alpine

然后 build 时使用 ECR 里的 base image。

preferred Dockerfile#

推荐把 Dockerfile 改成支持 build arg,这样不需要在 build 时修改文件内容。

ARG NODE_BASE_IMAGE=node:22-alpine
FROM ${NODE_BASE_IMAGE}

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
CMD ["node", "server.js"]

对应 buildspec.yml

version: 0.2

phases:
  pre_build:
    commands:
      - aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"
      - export IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG"
      - export BASE_IMAGE_REPOSITORY="${BASE_IMAGE_REPOSITORY:-base/node}"
      - export BASE_IMAGE_TAG="${BASE_IMAGE_TAG:-22-alpine}"
      - export BASE_IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$BASE_IMAGE_REPOSITORY:$BASE_IMAGE_TAG"
      - echo "IMAGE_URI=$IMAGE_URI"
      - echo "BASE_IMAGE_URI=$BASE_IMAGE_URI"
      - docker pull "$BASE_IMAGE_URI"
  build:
    commands:
      - docker build --build-arg NODE_BASE_IMAGE="$BASE_IMAGE_URI" -t "$IMAGE_URI" .
  post_build:
    commands:
      - docker push "$IMAGE_URI"
      - printf '{"imageUri":"%s"}\n' "$IMAGE_URI" > image-detail.json

patch existing Dockerfile during build#

如果暂时不想改应用仓库里的 Dockerfile,也可以在 CodeBuild 里临时把 FROM node:22-alpine 替换成 ECR base image。

version: 0.2

phases:
  pre_build:
    commands:
      - aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"
      - export IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPOSITORY:$IMAGE_TAG"
      - export BASE_IMAGE_REPOSITORY="${BASE_IMAGE_REPOSITORY:-base/node}"
      - export BASE_IMAGE_TAG="${BASE_IMAGE_TAG:-22-alpine}"
      - export BASE_IMAGE_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$BASE_IMAGE_REPOSITORY:$BASE_IMAGE_TAG"
      - echo "IMAGE_URI=$IMAGE_URI"
      - echo "BASE_IMAGE_URI=$BASE_IMAGE_URI"
      - docker pull "$BASE_IMAGE_URI"
  build:
    commands:
      - cp Dockerfile Dockerfile.original
      - sed -i "/^FROM /s|node:22-alpine|${BASE_IMAGE_URI}|g" Dockerfile
      - grep -n '^FROM ' Dockerfile
      - docker build -t "$IMAGE_URI" .
  post_build:
    commands:
      - docker push "$IMAGE_URI"
      - printf '{"imageUri":"%s"}\n' "$IMAGE_URI" > image-detail.json

Notes:

preferred:
    use Dockerfile ARG
    source code stays explicit and reviewable

temporary workaround:
    sed patch is acceptable for migration
    grep FROM line before docker build so logs show the actual base image

BASE_IMAGE_REPOSITORY:
    default: base/node
    can be passed from start-build env override if your base image repo differs

BASE_IMAGE_TAG:
    default: 22-alpine
    should match the image tag already pushed to ECR

IAM:
    CodeBuild service role must have ECR pull permission on base/node
    CodeBuild service role must have ECR push permission on application repo

6. Shell Script Start Build From Git Tag#

目标:脚本同步代码仓库,按 Git tag 打源码 zip 上传 S3,启动 CodeBuild,传环境变量进去,轮询等待 build 结果。成功后,再检查 ECR 中是否存在对应 tag 的镜像。

Assumptions:

local machine / CI runner:
    aws cli installed and configured
    git installed
    zip installed

repository:
    buildspec.yml exists at repo root
    Dockerfile exists at repo root

CodeBuild:
    project already exists
    project can use S3 source override
    project service role can read S3 source zip and push ECR

run-codebuild-from-tag.sh:

#!/usr/bin/env bash

# Exit immediately when any command fails.
set -euo pipefail

# Set the AWS region used by S3, CodeBuild, and ECR.
AWS_REGION="ap-east-1"

# Set the AWS account ID that owns the CodeBuild project and ECR repository.
AWS_ACCOUNT_ID="111122223333"

# Set the CodeBuild project name to start.
CODEBUILD_PROJECT="order-api-build"

# Set the Git repository URL to sync.
GIT_REPO_URL="git@github.com:example/order-api.git"

# Set the Git tag to build.
GIT_TAG="v1.2.3"

# Set the local work directory used by this script.
WORK_DIR="/tmp/codebuild-order-api"

# Set the S3 bucket used for CodeBuild source zip files.
SOURCE_BUCKET="my-codebuild-source-bucket"

# Set the S3 prefix used for source zip files.
SOURCE_PREFIX="codebuild-sources/order-api"

# Set the ECR repository where CodeBuild should push the Docker image.
ECR_REPOSITORY="order-api"

# Convert the Git tag to a Docker-safe image tag.
IMAGE_TAG="${GIT_TAG#v}"

# Build the local repository directory path.
REPO_DIR="${WORK_DIR}/repo"

# Build the local zip file path.
ZIP_FILE="${WORK_DIR}/${ECR_REPOSITORY}-${GIT_TAG}.zip"

# Build the S3 object key for this source zip.
S3_KEY="${SOURCE_PREFIX}/${ECR_REPOSITORY}-${GIT_TAG}.zip"

# Build the full S3 URI used by aws s3 cp.
S3_URI="s3://${SOURCE_BUCKET}/${S3_KEY}"

# Build the CodeBuild S3 source location format.
CODEBUILD_SOURCE_LOCATION="${SOURCE_BUCKET}/${S3_KEY}"

# Build the full expected ECR image URI.
ECR_IMAGE_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:${IMAGE_TAG}"

# Create the work directory if it does not exist.
mkdir -p "${WORK_DIR}"

# Clone the repository if the local checkout does not exist.
test -d "${REPO_DIR}/.git" || git clone "${GIT_REPO_URL}" "${REPO_DIR}"

# Enter the local repository checkout.
cd "${REPO_DIR}"

# Fetch all remote refs and tags so the requested tag is available.
git fetch --all --tags --prune

# Checkout the exact Git tag for a reproducible source package.
git checkout --force "${GIT_TAG}"

# Remove any stale local zip from previous runs.
rm -f "${ZIP_FILE}"

# Create a source zip from the checked-out tag and exclude the .git directory.
zip -r "${ZIP_FILE}" . -x ".git/*"

# Upload the source zip to S3 for CodeBuild to consume.
aws s3 cp "${ZIP_FILE}" "${S3_URI}" --region "${AWS_REGION}"

# Start CodeBuild with S3 source override and environment variable overrides.
BUILD_ID="$(aws codebuild start-build --project-name "${CODEBUILD_PROJECT}" --source-type-override S3 --source-location-override "${CODEBUILD_SOURCE_LOCATION}" --environment-variables-override name=AWS_ACCOUNT_ID,value="${AWS_ACCOUNT_ID}",type=PLAINTEXT name=AWS_REGION,value="${AWS_REGION}",type=PLAINTEXT name=ECR_REPOSITORY,value="${ECR_REPOSITORY}",type=PLAINTEXT name=IMAGE_TAG,value="${IMAGE_TAG}",type=PLAINTEXT name=GIT_TAG,value="${GIT_TAG}",type=PLAINTEXT --region "${AWS_REGION}" --query 'build.id' --output text)"

# Print the build ID for later troubleshooting.
echo "Started CodeBuild build: ${BUILD_ID}"

# Poll CodeBuild until the build reaches a terminal state.
while true; do
  # Query the current build status from CodeBuild.
  BUILD_STATUS="$(aws codebuild batch-get-builds --ids "${BUILD_ID}" --region "${AWS_REGION}" --query 'builds[0].buildStatus' --output text)"

  # Print the current build status.
  echo "CodeBuild status: ${BUILD_STATUS}"

  # Stop polling when the build succeeded.
  if [ "${BUILD_STATUS}" = "SUCCEEDED" ]; then
    # Exit the polling loop on success.
    break
  fi

  # Fail the script when the build reaches a failed terminal state.
  if [ "${BUILD_STATUS}" = "FAILED" ] || [ "${BUILD_STATUS}" = "FAULT" ] || [ "${BUILD_STATUS}" = "STOPPED" ] || [ "${BUILD_STATUS}" = "TIMED_OUT" ]; then
    # Print the final failed build status.
    echo "CodeBuild failed with status: ${BUILD_STATUS}" >&2

    # Exit with non-zero code so CI/CD marks this run as failed.
    exit 1
  fi

  # Wait before polling again to avoid excessive API calls.
  sleep 10
done

# Verify that the expected image tag exists in ECR after the build succeeds.
aws ecr describe-images --repository-name "${ECR_REPOSITORY}" --image-ids imageTag="${IMAGE_TAG}" --region "${AWS_REGION}" >/dev/null

# Print the pushed image URI when ECR verification succeeds.
echo "Image exists in ECR: ${ECR_IMAGE_URI}"

Run:

# Make the script executable.
chmod +x run-codebuild-from-tag.sh

# Run the script to package source, start CodeBuild, wait for result, and verify ECR.
./run-codebuild-from-tag.sh

Script notes:

source zip:
    uploaded to s3://my-codebuild-source-bucket/codebuild-sources/order-api/order-api-v1.2.3.zip

CodeBuild source override:
    --source-type-override S3
    --source-location-override bucket/key.zip

env override:
    passes AWS_ACCOUNT_ID, AWS_REGION, ECR_REPOSITORY, IMAGE_TAG, GIT_TAG

ECR check:
    describe-images imageTag=<IMAGE_TAG>
    proves CodeBuild pushed the expected image tag

7. Monitoring#

must watch:
    build failures
    queued duration
    build duration
    timeout count
    CloudWatch Logs errors
    ECR push failures
    Docker Hub or package registry rate limit

Useful commands:

aws codebuild batch-get-builds --ids <build-id> --region ap-east-1
aws logs tail /aws/codebuild/order-api-build --follow --region ap-east-1
aws ecr describe-images --repository-name order-api --region ap-east-1

8. Troubleshooting#

Symptom Check
AccessDenied on start-build deploy caller lacks codebuild:StartBuild
source object not found S3 bucket/key or region is wrong
build cannot download source CodeBuild service role lacks s3:GetObject
Cannot connect to Docker daemon project not running with privileged mode
ECR login fails service role lacks ecr:GetAuthorizationToken
Docker push denied service role lacks ECR upload/put image actions
final ECR check fails build did not push expected IMAGE_TAG

Failure diagnostics:

aws codebuild batch-get-builds --ids <build-id> --region ap-east-1
aws logs tail /aws/codebuild/order-api-build --since 1h --region ap-east-1
aws s3 ls s3://my-codebuild-source-bucket/codebuild-sources/order-api/
aws ecr describe-images --repository-name order-api --region ap-east-1

9. Checklist#

1. buildspec.yml exists at source root
2. Dockerfile exists at source root
3. source zip contains file contents at root, not an extra parent folder
4. deploy caller can upload S3, start CodeBuild, and describe ECR images
5. CodeBuild service role can read S3 source and push ECR
6. CodeBuild project has privileged mode enabled for Docker build
7. environment variables passed by start-build match buildspec.yml
8. CloudWatch Logs group is enabled
9. ECR image tag is checked after successful build