CertNote/ Commute Study Notes
Roadmap
KOEN
CLF-C02 · FoundationalCloud Practitioner - Foundational
DVA-C02 · AssociateDeveloper - Associate
SAA-C03 · AssociateSolutions Architect - Associate
SOA-C02 · AssociateCloudOps Engineer - Associate
SAP-C02 · ProfessionalSolutions Architect - Professional
DOP-C02 · ProfessionalDevOps Engineer - Professional
  • Week 1
    • 1.DevOps as an Operating Model: The Five Axes of CALMS and the Truth DORA Proved Through Measurement
    • 2.Well-Architected Framework: Rereading It Through the Six Lenses of DevOps
    • 3.The AWS DevOps Tool Map: The Code* Series and the Real Picture Beyond It
    • 4.Multi-Account Strategy: The Real Picture of Organizations, Control Tower, and IAM Identity Center
    • 5.Week 1 Wrap-Up: Cementing the DevOps Thinking Frame with Scenarios
  • Week 2
  • Week 3
    • 1.The Real Meaning of buildspec.yml: The Moment a Pipeline Specification Becomes Code
    • 2.The Physics of Build Speed: Trade-offs Among Cache, Parallelism, and Compute
    • 3.Design Principles of Secret Management: The Criteria That Separate Secrets Manager from Parameter Store
    • 4.VPC CodeBuild, Custom Images, ARM/Graviton: Expanding the Boundary of the Build Environment
    • 5.Week 3 Review: Integrated CodeBuild Scenarios and Practical Judgment
  • Week 4
  • Week 5
  • Week 6
  • Week 7
  • Week 8
  • Week 9
  • Week 10
  • Week 11
  • Week 12
  • Week 13
  • Week 14
  • Week 15
  • Week 16
Linux Master 2 · 2급KAIT Linux Master - Level 2
SCS-C03 · SpecialtySecurity - Specialty
Linux Master 1 · 1급KAIT Linux Master - Level 1
MLA-C01 · AssociateMachine Learning Engineer - Associate
AIF-C01 · FoundationalAI Practitioner - Foundational
DEA-C01 · AssociateData Engineer - Associate
MLS-C01 · SpecialtyMachine Learning - Specialty
SQLD · 개발자SQL Developer (SQLD)

© CertNote · 주간랩스

  • About
  • Changelog
  • Contact
  • Terms
  • Privacy
← DOP-C02/Week 3/Day 1

Based on the DOP-C02 exam guide·Report an error

DOP-C02· ProWeek 3 · Day 1~32 min read

Day 1 - The Real Meaning of buildspec.yml: The Moment a Pipeline Specification Becomes Code

Before the idea of CI infrastructure — the forerunner of CodeBuild — was first mentioned at AWS re:Invent in 2011, AWS users typically either ran Jenkins directly on EC2 or used an external SaaS CI service. The problem was that Jenkins was "a server you had to install." You had to manage the server, plugins conflicted, and when the build queue backed up you had to upgrade to a bigger instance. When CodeBuild was officially released in 2016, AWS took a different approach — spin up the build environment itself as a container, and throw it away when the build finishes. And "how do we build this" was to be captured in a single file called buildspec.yml.

This is the CodeBuild version of Pipeline-as-Code. Understanding buildspec.yml isn't simply memorizing YAML syntax. It's understanding the philosophy that "the build procedure becomes code," and grasping how security, caching, artifacts, and test visualization interlock on top of it.

The Full Structure of buildspec.yml: A Build Contract You Can Read in One File

version: 0.2  # 0.1 was deprecated in 2017. Always use 0.2.
 
env:
  variables:
    NODE_ENV: production          # plaintext environment variable
  parameter-store:
    DB_HOST: /myapp/prod/db-host  # SSM Parameter Store fetch
  secrets-manager:
    DB_PASS: prod/db:password::AWSCURRENT
    #         secretId:jsonKey:versionStage:versionId
  exported-variables:
    - BUILD_ID                    # passed to the next Pipeline Stage
 
phases:
  install:
    runtime-versions:
      nodejs: 20
      python: 3.11
    commands:
      - npm ci
  pre_build:
    commands:
      - aws ecr get-login-password | docker login --username AWS --password-stdin $ECR
  build:
    commands:
      - docker build -t myapp:$CODEBUILD_BUILD_NUMBER .
  post_build:
    commands:
      - docker push $ECR/myapp:$CODEBUILD_BUILD_NUMBER
    on-failure: ABORT
 
reports:
  unit-tests:
    files: ['reports/junit-*.xml']
    file-format: JUNITXML
 
artifacts:
  files:
    - dist/**/*
    - appspec.yml
  base-directory: build
  secondary-artifacts:
    docs:
      files: docs/**/*
 
cache:
  paths:
    - node_modules/**/*
    - /root/.m2/**/*

version: 0.2 looks trivial but it actually matters. 0.1 and 0.2 differ in environment-variable naming rules and in some phase behaviors. Since 2017, official AWS documentation only describes 0.2, and the exam assumes 0.2 as well.

💡 Related theory: buildspec.yml is a declarative build definition. Where a Makefile is imperative in style ("run these commands in order"), buildspec takes the form of a contract: "in this phase, these commands must run." This philosophy sits in the same lineage as Kubernetes Pod specs and Docker Compose. All of them follow the paradigm of "describe the desired state and the system achieves it."

🔍 Going deeper: buildspec.yml is version-controlled. Put it at the root of the code repository and git blame tracks "when and by whom the build procedure was changed." The biggest problem with pre-2015 Jenkins GUI pipelines was that configuration lived in the Jenkins database and could not be version-controlled. buildspec.yml solves that problem — the build procedure becomes subject to PR review.

Phase Execution Order and Failure Handling: The Logic of the Container Lifecycle

CodeBuild spins up a new container for a single build, runs it in the following order, and deletes the container when it's done.

[Container Provision] → [Source Download] → [Restore Cache]
  → install → pre_build → build → post_build
  → [reports] → [artifacts] → [Save Cache]
  → [Container Terminate]
PhaseDefault behavior on failureHow to changePractical use
installABORT (later phases skipped)on-failure: CONTINUEruntime install failure = build is meaningless
pre_buildABORTsameECR login, environment validation
buildpost_build still runson-failure: ABORT/CONTINUEactual build, tests
post_buildaffects the build resultsamepush, artifact cleanup

The reason post_build runs even when the build phase fails is for "cleanup code." Work such as Docker logout, temporary file cleanup, and failure notifications must always happen regardless of whether the build succeeded.

post_build:
  commands:
    - docker push $ECR/myapp:$IMAGE_TAG
  finally:                          # always runs, success or failure
    - docker logout $ECR
    - echo "Build complete: $CODEBUILD_BUILD_ID"

A finally block can be placed inside each phase. This means exactly the same thing as Java's try-finally — execution is guaranteed regardless of whether an exception occurred.

⚠️ Pitfall: If you set on-failure: CONTINUE on the build phase, the pipeline can keep going even when the build fails. Incidents where a test failure is ignored and the change goes all the way to deployment originate from this setting. on-failure should only be used for "CONTINUE for resource cleanup"; for core build steps, the default (ABORT) is correct.

💡 Related theory: The phase order maps exactly onto the traditional stages of a software build. Configure → Compile → Test → Package maps to install → build → post_build. pre_build comes from the CI/CD best practice of "precondition validation" — first verify the environment is correct, then start the real work.

Three Tiers of Environment Variables: The Layered Structure of Security

MethodStoreExposure scopeIAM permission
env.variablesplaintext in buildspeccan be exposed in logsnone
env.parameter-storeSSM Parameter Storenot exposed in logsssm:GetParameter
env.secrets-managerSecrets Managernever exposed in logssecretsmanager:GetSecretValue + kms:Decrypt
env:
  parameter-store:
    DB_HOST: /myapp/prod/db-host          # String or SecureString
  secrets-manager:
    DB_PASS: prod/db-secret:password::AWSCURRENT
    #         ^secret-id  ^json-key ^version-stage
    API_KEY: prod/api-key

In the Secrets Manager reference format secret-id:json-key:version-stage:version-id, the json-key extracts a specific field when the secret is in JSON form. For a secret of {"username":"admin","password":"secret123"}, prod/db:password extracts only secret123.

The CodeBuild Service Role needs two permissions:

  1. secretsmanager:GetSecretValue — retrieve the secret value
  2. kms:Decrypt — decrypt a secret encrypted with a CMK (Customer Managed Key)

If you use the AWS managed key (aws/secretsmanager) instead of a CMK, kms:Decrypt is implicitly allowed; but in organizations whose security policy mandates CMKs, a missing version of this permission is the single most common cause of failure.

🔍 Going deeper: Secrets Manager environment-variable injection happens exactly once, at build start. Even if the secret rotates while the build runs for 30 minutes, the value already loaded into memory does not change. However, if code calls GetSecretValue again via the SDK during the build, it receives the new value. In environments where the secret rotation period overlaps with build duration, this distinction matters.

📚 Case study: The 2019 Capital One data breach was caused by credential theft through the EC2 metadata service. After that incident, AWS made IMDSv2 mandatory and pushed Secrets Manager even harder as the standard for "never putting credentials in code." buildspec's env.secrets-manager is precisely the practical tool of that lesson — a structural enforcement that makes it impossible for a developer to write a password directly into the buildspec.

The Reports Block: Managing Test Results Alongside Code

reports:
  pytest_reports:
    files: ['reports/pytest.xml']
    file-format: JUNITXML         # JUNITXML, NUNITXML, CUCUMBERJSON, TESTNGXML, VISUALSTUDIOTRX
  coverage:
    files: ['coverage.xml']
    file-format: COBERTURAXML     # COBERTURAXML, JACOCOXML, CLOVERXML, SIMPLECOV

Supported formats:

  • Test results: JUnitXML, NUnitXML, CucumberJSON, TestNG XML, VisualStudio TRX
  • Code coverage: Clover, Cobertura, JaCoCo XML, SimpleCov

When the Reports block is present, the CodeBuild console gains per-build Pass/Fail visualization and history tracking. The difference between "printing test results as text in a log" and "tracking them as structured reports" in a CI pipeline is exactly this block.

💡 Related theory: Structured test reports implement the software-engineering principle of Test Visibility. Jez Humble's "Continuous Delivery" (2010) stresses that "test results must be immediately visible to every team member." A text log has to be parsed by a human, but JUnit XML is aggregated automatically by tooling. Measuring the DORA 4 metrics' "Change Failure Rate" also requires this kind of structured data to be automated.

The Artifacts Block: The Flow of Build Outputs

artifacts:
  files:
    - 'dist/**/*'
    - appspec.yml
    - taskdef.json
  name: build-$(date +%Y%m%d-%H%M%S)   # custom S3 object key
  base-directory: build                  # relative paths are based on this directory
  discard-paths: no                      # yes ignores the directory structure
  secondary-artifacts:
    sourcemap:
      files: '**/*.map'
      base-directory: build/source-maps
    sbom:
      files: 'sbom.json'

The primary artifact flows to the next Stage of CodePipeline (InputArtifact). Secondary artifacts are stored in a separate S3 location and are used for analysis, auditing, and documentation.

The pattern of exporting an SBOM (Software Bill of Materials) as a secondary artifact has recently become important because of SLSA (Supply chain Levels for Software Artifacts) compliance. Since the 2021 US executive order (EO 14028) required SBOMs for software procured by the federal government, many companies have begun adopting automatic SBOM generation during the build as a standard.

💡 Related theory: The SLSA (Supply chain Levels for Software Artifacts) framework is a software supply-chain security model proposed by Google in 2021. Level 1 is "a build script exists and is version-controlled," Level 2 is "the build is reproducible and signed," Level 3 is "the build environment is isolated," and Level 4 is "fully reproducible and auditable." CodeBuild satisfies SLSA Level 2 by default — it runs in an isolated container, build logs are retained in CloudWatch, and the buildspec lives in Git.

CODEBUILD_* Built-in Environment Variables: Accessing Build Context

VariableMeaningUse
CODEBUILD_BUILD_ID"project:uuid" formatbuild tracking ID
CODEBUILD_BUILD_NUMBERinteger incrementing from 1image tag
CODEBUILD_BUILD_ARNbuild ARNaudit logs
CODEBUILD_SOURCE_VERSIONgit SHA / tag / PR numbercode tracking
CODEBUILD_RESOLVED_SOURCE_VERSIONthe actually resolved git SHAreproducibility guarantee
CODEBUILD_WEBHOOK_TRIGGER"branch/main", "pr/42", etc.conditional builds
CODEBUILD_INITIATOR"user/dev" or "codepipeline/..."origin tracking
pre_build:
  commands:
    - export IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c1-8)
    # first 8 characters of the git SHA as the image tag — reproducibility and traceability at once

The difference between CODEBUILD_SOURCE_VERSION and CODEBUILD_RESOLVED_SOURCE_VERSION: the former may be a branch name like "main," while the latter is always the actual commit SHA. For image tags and artifact names, using the Resolved version is better for reproducibility.

Build Batch: Expanding One Build Across Multiple Dimensions

Running builds in parallel, or as a dependency graph, is Build Batch.

batch:
  fast-fail: true          # if one fails, abort the rest
  build-list:              # simple parallel (no dependencies)
    - identifier: build_arm
      env:
        compute-type: BUILD_GENERAL1_LARGE
        type: ARM_CONTAINER
        image: aws/codebuild/amazonlinux2-aarch64-standard:3.0
    - identifier: build_x86
      env:
        type: LINUX_CONTAINER
        image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
  build-graph:             # dependency DAG (ordered)
    - identifier: lint
    - identifier: unit_test
    - identifier: integration_test
      depend-on: [unit_test]
    - identifier: package
      depend-on: [lint, integration_test]
  build-matrix:            # auto-generate variable combinations
    static:
      ignore-failure: false
    dynamic:
      buildspec:
        - build/node18.yml
        - build/node20.yml
      env:
        variables:
          ARCH: [amd64, arm64]

build-list is simple parallelism, build-graph defines dependencies as a DAG (Directed Acyclic Graph), and build-matrix automatically generates builds from the Cartesian product of variable combinations.

🔍 Going deeper: The dependency graph of Build Batch is implemented on top of topological sort from compiler theory. The way each build node waits for its dependency nodes to complete is the same algorithm as GNU Make's dependency handling. The difference is that CodeBuild runs each node as a separate container — there is no shared filesystem between nodes. So passing artifacts between nodes must go through S3, and this is why the S3 cache is especially important in Batch builds.

📚 Case study: As of 2022, Netflix runs thousands of builds per day. While standardizing multi-architecture images (ARM/x86), it aggressively adopted the Build Batch pattern. According to a blog post published by Netflix's "Metaflow" ML platform team (2023), after adopting ARM builds, build costs dropped by about 18% and inference performance improved on ARM-based EKS nodes. The lesson was that aligning build and runtime architectures increases the accuracy of performance measurement.

Debug Session and Local Build: Two Ways to Dig Into Build Failures

Debug Session (remote):

# Start the build in debug mode
aws codebuild start-build \
  --project-name myproj \
  --debug-session-enabled
 
# Connect to the build container with SSM Session Manager
aws ssm start-session --target codebuild:<build-id>

The build pauses at the failing phase and you can connect to the container. Up to 7 hours.

Local Build (local):

# Run the same environment locally with the CodeBuild Agent
./codebuild_build.sh \
  -i aws/codebuild/standard:7.0 \
  -a /tmp/artifacts \
  -e .env.local

Same idea as GitHub Actions' act tool — reproduce the identical environment locally to debug CI failures.

🎯 Scenario: A team's build fails "only in CodeBuild" and succeeds locally. The order of root-cause investigation is (1) identify the failing phase in CloudWatch Logs → (2) connect to the container with a Debug Session and run the same commands manually → (3) check for environment-variable differences (env | sort) → (4) reproduce the buildspec.yml with a local Local Build. This sequence is the standard troubleshooting flow.

Comparison with GCP Cloud Build: A Difference in Design Philosophy

ItemAWS CodeBuildGCP Cloud Build
Config filebuildspec.ymlcloudbuild.yaml
Unit of executionPhase (install/build/...)Step (independent container)
CacheS3 / Local DockerGCS / Docker Layer
SecretsSecrets Manager / Parameter StoreSecret Manager
Parallel buildsBuild Batch (graph/list/matrix)parallelism (step level)
ARM supportaarch64 standard imagesarm machine types
VPC integrationNative (creates an ENI)Private Pool
Pricing modelper-minute billing (by compute type)per-minute billing (by machine type)

The biggest philosophical difference: CodeBuild runs ordered stages called "phases" inside a single container. In Cloud Build, each "Step" is an independent container — extremely flexible, but file sharing between Steps must go through the /workspace volume. CodeBuild's phase model is simpler and doesn't carry the complexity of "you must manage state between Steps via a volume."

💡 Related theory: CodeBuild's phase-based model and Cloud Build's Step-based model reflect the software-engineering difference between procedural and component-based architectures. The procedural model is easy to understand but has low reusability; the component-based model has high reusability but comes with composition complexity. GitHub Actions took a third path with an ecosystem of reusable components at the "Action" level.

exported-variables: Beyond Phases, Into Stages

phases:
  build:
    commands:
      - export VERSION=$(cat version.txt)
      - export IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c1-8)
env:
  exported-variables:
    - VERSION
    - IMAGE_TAG

In CodePipeline V2, the next Stage references these values as #{BuildAction.IMAGE_TAG}. For example, the ECS Action in a Deploy Stage can receive the image URI dynamically. This is not supported in V1 pipelines.

Why this matters: the entire pipeline gets to share "an immutable artifact identifier determined at build time." The image tag the build produced flows consistently into the test Stage, the approval Stage, and the deploy Stage.

A Full buildspec Example: Production-grade Integration

version: 0.2
 
env:
  parameter-store:
    GIT_TOKEN: /myapp/github/pat
  secrets-manager:
    DB_PASS: prod/db:password
    ECR_REGISTRY: prod/ecr:registry
  exported-variables:
    - IMAGE_TAG
    - BUILD_VERSION
 
phases:
  install:
    runtime-versions:
      nodejs: 20
      docker: 20
    commands:
      - npm install -g pnpm
      - aws codeartifact login --tool npm --repository prod --domain my-org
 
  pre_build:
    commands:
      - echo "Build $CODEBUILD_BUILD_NUMBER from $CODEBUILD_RESOLVED_SOURCE_VERSION"
      - export IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c1-8)
      - export BUILD_VERSION=$(cat package.json | jq -r '.version')-$IMAGE_TAG
      - aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REGISTRY
      - docker buildx create --use --name builder
 
  build:
    commands:
      - pnpm install --frozen-lockfile
      - pnpm test --reporter=junit --output-file=reports/junit.xml
      - pnpm build
      - docker buildx build \
          --platform linux/amd64,linux/arm64 \
          --cache-from type=registry,ref=$ECR_REGISTRY/myapp:cache \
          --cache-to type=registry,ref=$ECR_REGISTRY/myapp:cache,mode=max \
          --push \
          -t $ECR_REGISTRY/myapp:$IMAGE_TAG \
          -t $ECR_REGISTRY/myapp:latest .
 
  post_build:
    commands:
      - aws ecs describe-task-definition --task-definition myapp --query taskDefinition > taskdef.json
      - sed -i "s|<IMAGE>|$ECR_REGISTRY/myapp:$IMAGE_TAG|g" appspec.yml
    finally:
      - docker logout $ECR_REGISTRY
      - echo "Build complete: $BUILD_VERSION"
 
reports:
  unit:
    files: ['reports/junit.xml']
    file-format: JUNITXML
  coverage:
    files: ['coverage/cobertura.xml']
    file-format: COBERTURAXML
 
artifacts:
  files:
    - taskdef.json
    - appspec.yml
  secondary-artifacts:
    sbom:
      files: 'sbom.json'
    sourcemap:
      files: '**/*.map'
      base-directory: build
 
cache:
  paths:
    - /root/.npm/**/*
    - /root/.docker/**/*

Wrapping up: beyond making the build procedure into code, buildspec.yml is also the single document through which the whole team shares "how this gets built." Bugs that reproduce only in the CI environment, incidents where a password gets printed into logs, confusion where image tags get mixed up — these three are the most common problems that a correctly designed buildspec can prevent. Tomorrow we move into the caching strategies that make this pipeline fast.

📝 Practice Questions

Click a choice to reveal the answer and explanation.

Question 1

Which of the following correctly describes the behavior of post_build when the build phase fails?

Question 2

You referenced a secret in buildspec via env.secrets-manager and the build fails with "AccessDenied: decryption." What is the most likely cause?

Question 3

Which most accurately describes the difference between Build Batch's build-graph and build-list?

Question 4

How do you build ARM (Graviton) and x86 images simultaneously from a single buildspec?

Question 5

In CodePipeline V2, how do you use the build stage's IMAGE_TAG variable in the deploy stage?

Question 6

From an SLSA (Supply chain Levels for Software Artifacts) perspective, what security benefit does keeping buildspec.yml in a Git repository provide?

Question 7

What is the most direct way to debug an error that reproduces "only inside the CodeBuild container"?

PreviousWeek 2 Wrap-Up: From Source Control to Code Signing, Cementing the DevSecOps Mindset Through ScenariosWeek 2 · Day 5Next The Physics of Build Speed: Trade-offs Among Cache, Parallelism, and ComputeWeek 3 · Day 2

Official references

  • DOP-C02 official exam page(opens in a new tab)
  • AWS CodeBuild documentation(opens in a new tab)
  • AWS Secrets Manager documentation(opens in a new tab)
  • Amazon S3 documentation(opens in a new tab)
  • Amazon CloudWatch documentation(opens in a new tab)
  • AWS CodePipeline documentation(opens in a new tab)

On this page

  • The Full Structure of buildspec.yml: A Build Contract You Can Read in One File
  • Phase Execution Order and Failure Handling: The Logic of the Container Lifecycle
  • Three Tiers of Environment Variables: The Layered Structure of Security
  • The Reports Block: Managing Test Results Alongside Code
  • The Artifacts Block: The Flow of Build Outputs
  • CODEBUILD_* Built-in Environment Variables: Accessing Build Context
  • Build Batch: Expanding One Build Across Multiple Dimensions
  • Debug Session and Local Build: Two Ways to Dig Into Build Failures
  • Comparison with GCP Cloud Build: A Difference in Design Philosophy
  • exported-variables: Beyond Phases, Into Stages
  • A Full buildspec Example: Production-grade Integration
  • Practice Questions