Back to notes

Source code is in the pipeline. The Build stage hands it to CodeBuild, which installs dependencies and builds the site. The built files become an output artifact for the Deploy stage.

CodeBuild Service Role

CodeBuild needs its own IAM role, separate from the pipeline’s role in post 8. This role grants CodeBuild permission to write logs and access artifacts.

  CodeBuildServiceRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
            Action: 'sts:AssumeRole'
      Policies:
        - PolicyName: CodeBuildAccess
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/codebuild/${AWS::StackName}-Build*'
              - Effect: Allow
                Action:
                  - s3:GetObject
                  - s3:PutObject
                  - s3:GetBucketVersioning
                Resource:
                  - !Sub '${PipelineArtifactBucket.Arn}/*'
                  - !GetAtt PipelineArtifactBucket.Arn

AssumeRolePolicyDocument is the trust policy. It allows the codebuild.amazonaws.com service principal to assume this role.

The Policies array contains one inline policy with two statements.

The first statement grants CloudWatch Logs permissions scoped to the build project’s log group (/aws/codebuild/${AWS::StackName}-Build). Without these, builds fail silently with no output to debug.

The second statement grants S3 permissions scoped to the artifact bucket only. CodeBuild reads the source artifact and writes the build artifact back.

CodeBuild Project

The AWS::CodeBuild::Project resource defines the build environment and commands. Everything runs inside a managed container, so there’s nothing to install or maintain.

  BuildProject:
    Type: 'AWS::CodeBuild::Project'
    Properties:
      Name: !Sub '${AWS::StackName}-Build'
      ServiceRole: !GetAtt CodeBuildServiceRole.Arn
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
      Cache:
        Type: LOCAL
        Modes:
          - LOCAL_CUSTOM_CACHE
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
            install:
              runtime-versions:
                nodejs: 20
              commands:
                - npm ci
            build:
              commands:
                - npm run build
          artifacts:
            base-directory: dist
            files:
              - '**/*'
          cache:
            paths:
              - 'node_modules/**/*'

A few things to note:

Artifacts: Type: CODEPIPELINE and Source: Type: CODEPIPELINE tell CodeBuild that it receives input from and sends output to a pipeline. It doesn’t pull source or push artifacts on its own.

Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0 is a managed image with common runtimes pre-installed. The runtime-versions block in the buildspec selects Node.js 20 from it.

ComputeType: BUILD_GENERAL1_SMALL gives the build 3 GB of memory and 2 vCPUs. That’s plenty for a static site build.

Cache with LOCAL_CUSTOM_CACHE enables local caching on the build host. The buildspec cache.paths section tells CodeBuild which directories to persist. Here it caches node_modules so npm does not re-download every package on every run. The cache is best-effort. If CodeBuild provisions a fresh runner, the cache starts empty and packages are downloaded from scratch.

The buildspec has two phases:

  • install selects Node.js 20 and runs npm ci. Unlike npm install, npm ci uses the lockfile exactly, is faster in CI, and fails if package-lock.json is out of sync with package.json.
  • build runs npm run build. Replace this with whatever your framework uses.

artifacts tells CodeBuild to package everything in the dist directory. This is the standard output directory for Astro, Vite, and most static site generators. If your framework outputs to build or out, change base-directory to match.

cache.paths lists the directories CodeBuild should persist between builds. node_modules/**/* covers the full dependency tree. This only works with LOCAL_CUSTOM_CACHE set in the resource’s Cache block.

Build Stage

The Build stage in the pipeline’s Stages array connects the Source output to the CodeBuild project.

        - Name: Build
          Actions:
            - Name: SiteBuild
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: '1'
              InputArtifacts:
                - Name: SourceArtifact
              OutputArtifacts:
                - Name: BuildArtifact
              Configuration:
                ProjectName: !Ref BuildProject

InputArtifacts references SourceArtifact from the Source stage. This is the zipped repo contents.

OutputArtifacts names the build output BuildArtifact. The Deploy stage uses this name to pick up the built files.

ProjectName: !Ref BuildProject tells the pipeline which CodeBuild project to run. !Ref returns the project name, not the ARN.