Based on the DOP-C02 exam guideReport an error
The single question running through this week was "how much do you treat the build environment like code?" buildspec.yml expresses the build procedure, cache strategy expresses build speed, secrets configuration expresses the security boundary, and VPC mode expresses the network boundary — all as code. Add ARM builds and even architecture selection moves inside the build pipeline.
The DOP-C02 exam does not ask about these elements separately. It asks in integrated form: "given these constraints at this company, which configuration is correct?" Today we practice that integrated judgment. First we lay out the core comparison structures at a glance, then point out the trap patterns, then verify judgment with 12 scenario questions.
A weekly review must not become a mere summary. The goal is a level where you can explain to yourself "why A and why not B." If you can't explain it, you'll freeze in the exam room in front of two options that both look 90% right.
💡 The purpose of review: You learned the content of each Day of Week 3 independently, but real problems mix that knowledge. A problem like "there's a network error when using Secrets Manager in a VPC mode build" is only solvable if you know VPC + Secrets Manager + endpoints at the same time. Today is the day we build those connections.
The four phases of buildspec.yml (install → pre_build → build → post_build) have meaning in their order. install prepares the runtime itself, pre_build handles login and environment checks, build does the actual compilation and tests, and post_build handles image push, tagging, and notifications. The finally block runs even if the phase fails — use it for cleanup work (deleting temporary files, sending failure notifications).
version: 0.2
phases:
install:
runtime-versions:
java: corretto17
commands:
- echo "installing tools"
pre_build:
commands:
- aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URI
build:
commands:
- mvn test
- docker build -t $IMAGE_TAG .
post_build:
commands:
- docker push $IMAGE_TAG
finally:
- echo "build finished (regardless of success/failure)"
env:
secrets-manager:
DB_PASS: prod/myapp:db_password
exported-variables:
- IMAGE_TAG
reports:
JUnitResults:
files:
- '**/surefire-reports/*.xml'
file-format: JUNITXML
cache:
paths:
- '/root/.m2/**/*'🔍 Going deeper:
exported-variablesmatters when integrating with CodePipeline. It lets a variable produced by CodeBuild (e.g.,IMAGE_TAG) be referenced by the next Action. The pattern where, in an ECS Blue/Green pipeline, CodeBuild exports the image URI viaexported-variablesand the Deploy Action receives it and injects it into the Task Definition appears frequently on the exam.
💡 Related theory: The
finallyblock applies thetry-finallypattern from programming languages to build phases. Putting "cleanup work" that must run even on failure intofinallyis in the same spirit as the Resource Acquisition Is Initialization (RAII) principle — resources (logs, temporary files, external service state) must always be cleaned up after use.
Caching divides into "where you store it" and "what you cache."
| Cache type | Storage location | On host change | Suitable targets | Caution |
|---|---|---|---|---|
| Local Source | build host disk | miss | reducing Git source clone time | effective only in host-pinned environments |
| Local Docker Layer | build host Docker | miss | reusing Dockerfile layers | privilegedMode: true required |
| S3 Custom | S3 bucket | hit | node_modules, .m2, .gradle | watch object size (500MB+) |
| BuildKit + ECR Registry | ECR | hit | Docker layers (including npm install) | combine with ECR Pull Through Cache |
# S3 cache example
cache:
type: S3
location: my-codebuild-cache/java-build
paths:
- '/root/.m2/**/*'
- '/root/.gradle/caches/**/*'# Local Docker Layer cache (requires privilegedMode)
cache:
type: LOCAL
modes:
- LOCAL_DOCKER_LAYER_CACHE
- LOCAL_SOURCE_CACHE⚠️ Pitfall: When you push
node_modulesinto the S3 cache and the directory exceeds 500MB, S3 upload/download overhead becomes longer than thenpm installtime — a counterproductive result. In that case, splitting thenpm installlayer in the Dockerfile and using BuildKit + ECR Registry Cache is more efficient.
📚 Case study: In a large Java project (3GB+ of Maven dependencies), applying the S3 cache actually made builds slower. The cause was that the 3GB S3 download time (2 minutes) exceeded Maven dependency resolution (40 seconds). The fix was narrowing the S3 cache paths to include only internal artifacts, like
~/.m2/repository/com/mycompany/**/*.
Is it a secret value?
├─ Automatic rotation needed?
│ └─ YES → Secrets Manager ($0.40/secret/month)
│ ├─ RDS credentials: RDS Rotation Lambda auto-generated
│ ├─ Custom values: write your own Rotation Lambda
│ └─ buildspec: env.secrets-manager
└─ Simple configuration value, no rotation needed?
├─ Free tier sufficient? → Parameter Store Standard (free)
│ └─ buildspec: env.parameter-store
└─ Need TPS > 40? → Parameter Store Advanced ($0.05 per 10,000 API calls)
Cross-account scenario: for CodeBuild to read a secret in another account's Secrets Manager, you need
secretsmanager:GetSecretValue + kms:Decrypt permissions on the CodeBuild Service Role💡 Related theory: Secrets Manager's automatic rotation is structured so that a Rotation Lambda performs the actual replacement. It's a 4-step protocol in which the Lambda creates the new secret (
createSecret) → applies it to the DB (setSecret) → verifies it (testSecret) → activates it (finishSecret). Understanding this protocol explains "why there is no service downtime during rotation" — the new version under theAWSPENDINGlabel is promoted toAWSCURRENTonly after it has been prepared and verified.
🔍 Going deeper: Parameter Store's 40 TPS limit becomes a real bottleneck in large-scale Lambda environments. When hundreds of Lambdas read configuration from Parameter Store simultaneously during cold starts,
ThrottlingExceptionoccurs. Remedies: (1) upgrade to Parameter Store Advanced (higher TPS limit), (2) cache the configuration in memory at Lambda initialization (the same execution environment is reused), (3) use Secrets Manager (higher TPS limit).
CodeBuild in VPC mode cannot directly reach the general internet. Each external service needs an appropriate path.
[CodeBuild Container in Private Subnet]
│
├─ S3 (artifact, cache) → S3 Gateway Endpoint (free, route table)
├─ Secrets Manager → Interface Endpoint ($0.01/hour)
├─ KMS → Interface Endpoint ($0.01/hour)
├─ ECR API → Interface Endpoint ($0.01/hour)
├─ ECR DKR (Docker layers) → Interface Endpoint ($0.01/hour)
├─ CloudWatch Logs → Interface Endpoint or NAT Gateway
└─ External internet (npm, Maven Central, etc.) → NAT Gateway required
⚠️ Pitfall: "I turned on VPC mode and now I get S3 permission errors" is almost always caused by a missing S3 Gateway Endpoint. A Gateway Endpoint is not added to the route table automatically — you must attach it in the VPC configuration yourself and include it in the build subnet's route table. Unlike Interface Endpoints it's free, so always configure it for VPC builds.
📚 Case study: After one team migrated CodeBuild to VPC mode, build costs increased by $200 per month. Tracing the cause revealed data transfer costs for reaching S3 through the NAT Gateway. Adding an S3 Gateway Endpoint brought that cost down to $0. They also added an ECR Interface Endpoint to cut Docker layer pull costs. In VPC mode, network path optimization translates directly into cost.
| Item | AWS Managed Image | Custom Image (ECR) |
|---|---|---|
| Maintenance | AWS patches it | the team builds and pushes it |
| Build start speed | fast (pre-prepared) | can be slower (image pull) |
| Tool customization | limited | fully free |
imagePullCredentialsType | CODEBUILD | SERVICE_ROLE |
privilegedMode | usually unnecessary | required for Docker builds |
| Cost | none | ECR storage + transfer |
A Custom Image's imagePullCredentialsType: SERVICE_ROLE means the CodeBuild Service Role's permissions are used to pull the image from ECR. That Role needs ecr:GetDownloadUrlForLayer, ecr:BatchGetImage, and ecr:GetAuthorizationToken.
💡 Related theory: The core reason to use a Custom Image is build reproducibility. An image the team manages itself guarantees "the same tool versions and the same result no matter when you build." With an AWS Managed Image, build results can change when AWS updates it (e.g., Python 3.11.1 → 3.11.4 patch). In environments requiring audit trails, such as finance and healthcare, Custom Image + specifying the image digest (SHA256, not a tag) is the standard.
| Batch type | Structure | Suitable cases |
|---|---|---|
build-list | independent builds run in parallel | multi-architecture (x86/ARM), multi-service |
build-matrix | every combination of variables | multi-runtime × multi-OS combinations |
build-graph | a DAG with dependencies | guaranteeing common-build → dependent-build ordering |
# build-matrix example: Python 3.9/3.10/3.11 × Linux/ARM
batch:
build-matrix:
static:
ignore-failure: false
dynamic:
buildspec:
- buildspec_linux.yml
- buildspec_arm.yml
env:
variables:
PYTHON_VERSION:
- "3.9"
- "3.10"
- "3.11"This configuration runs 2 × 3 = 6 builds in parallel.
🔍 Going deeper:
build-graphsupports DAG (Directed Acyclic Graph) dependencies. It's the pattern of "build the shared library first, and on success build services A/B/C in parallel using that artifact." It's especially useful in monorepos. When you declare dependencies with thedepend-onfield, CodeBuild performs the topological sort automatically.
| Item | On-Demand | Reserved Capacity Fleet |
|---|---|---|
| Billing model | per build minute | per hour the Fleet is maintained (regardless of builds) |
| Provisioning wait | 10-30 seconds | nearly 0 seconds |
| Suitable environment | sporadic builds | continuous, high-frequency builds |
| Minimum Fleet size | N/A | 1 instance |
An example break-even calculation:
💡 Related theory: The economics of a Fleet is a choice between fixed cost and variable cost. When demand is predictable and continuous, fixed cost (Fleet) wins; when demand is irregular, variable cost (On-Demand) wins. It's the same logic as AWS Reserved Instances. If your build pattern "concentrates in specific hours of the day," consider setting different Fleet sizes per time window, or a Fleet that supports Auto Scaling.
Trap 1: "To go faster, raise the Compute Type"
Always wrong. I/O-bound builds (package downloads, S3 uploads, ECR pushes) don't get faster with more CPU. Measure CloudWatch's BuildDuration and per-phase times first, then apply the means appropriate to the bottleneck phase (cache, parallelism, network path).
Trap 2: "Secrets are fine in env.variables as long as they're KMS-encrypted"
env.variables is written in plaintext in buildspec.yml and exposed in build logs. Regardless of KMS encryption, writing the value directly into the buildspec risks leakage. Always use env.secrets-manager or env.parameter-store.
Trap 3: "Local Cache is always faster" If the build host changes, you get a cache miss. The S3 cache is more reliable. That said, the S3 cache also backfires when the object is too large due to download overhead.
Trap 4: "ARM builds are fine with QEMU" It works, but it's slow, and compiling native code (C/C++/Rust) can hit emulation instability. ARM_CONTAINER + native builds is the standard.
Trap 5: "In VPC mode, every AWS service is automatically reachable" Wrong. VPC mode only attaches the build container to the VPC. To reach each AWS service, you must configure a VPC Endpoint (Gateway or Interface) separately. If you need the internet, you also need a NAT Gateway.
Trap 6: "Secrets Manager is always better than Parameter Store" The costs differ. Using Secrets Manager for 95 simple configuration values that need no rotation costs $38/month, whereas Parameter Store Standard costs $0. Choose the tool according to the requirement (whether rotation is needed).
⚠️ Traps, consolidated: The common pattern in these traps is "picking an answer by tool name without looking at the requirements." The essential habit is to find the requirement keyword in the exam question first. "Automatic rotation" → Secrets Manager, "host-pinned" → Local Cache, "ARM workload" → ARM_CONTAINER, "private resources" → VPC mode + endpoints.
🎯 Scenario: A team reported "builds are slow." Looking at CloudWatch, of the total 8-minute build,
DOWNLOAD_SOURCEwas 2 minutes +PRE_BUILD(npm install) 4 minutes + actualBUILD2 minutes. Where do you optimize first? The answer is that PRE_BUILD (npm install) is the biggest bottleneck, so applying an S3 Cache or BuildKit + ECR Cache comes first.DOWNLOAD_SOURCEcan be reduced with a Local Source Cache, but that's ineffective when the build host changes, so also consider configuring a Git shallow clone. Upgrading the Compute Type has no effect in this case.
Connecting what we learned over the week into a single flow:
buildspec.yml → expresses the build procedure as code. Each phase has a clear role, and finally is for cleanup work.
Cache → the practical improvement to build speed. Whether the host is pinned and how large the objects are determine the strategy.
Secrets → secret values must always go through env.secrets-manager or env.parameter-store. Whether rotation is needed is what separates Secrets Manager from Parameter Store.
VPC mode → the only way to reach private resources. Endpoint design and subnet size determine operational quality.
Build Batch → the parallelization strategy. Independent builds use build-list, combinations use build-matrix, dependencies use build-graph.
Custom Image → used instead of an AWS Managed Image in environments where reproducibility matters. The SERVICE_ROLE credentials type is required.
Reserved Fleet → removes provisioning overhead in high-frequency builds. Calculating the break-even point between fixed and variable costs drives the adoption decision.
📚 Week 3 end-to-end case: A company builds 10 microservices in a monorepo. Each service needs x86 and ARM images (20 builds), it must also run DB migration SQL, and every password rotates every 30 days. The network is inside a private VPC. The correct combination for this scenario: Build Batch build-list (20 in parallel) + VPC mode (private DB access) + S3 Gateway Endpoint (artifact upload) + Secrets Manager with Rotation (DB passwords) + a mix of ARM_CONTAINER and LINUX_CONTAINER (multi-architecture) + Reserved Fleet (dozens of builds per day) + ECR Pull Through Cache (avoiding the Docker Hub rate limit).
Click a choice to reveal the answer and explanation.
Question 1
npm install takes 4 minutes on every build. The build host may differ every time. What is the most effective improvement?
Question 2
The build must run migration SQL against an Aurora PostgreSQL instance in a private subnet. What is the most appropriate configuration?
Question 3
The DB password rotates automatically every 30 days. The build must always run the DB migration with the currently valid password. What is the most appropriate configuration?
Question 4
Builds fail every morning due to the Docker Hub rate limit. What is the most effective long-term fix?
Question 5
You must build Java and Python services simultaneously and produce ARM/x86 images for each. Four build combinations are needed in total. What is the most efficient configuration?
Question 6
Provisioning takes 30 seconds per build and you run 300 builds per day. That's 150 hours per month wasted on provisioning. What is the most direct fix?
Question 7
Of 100 secrets, only 5 need rotation. What configuration minimizes monthly cost?
Question 8
Starting a VPC build produces the error The maximum number of network interfaces has been reached for subnet. What is the most appropriate response?
Question 9
You need to connect directly into the build container to debug a failure. What is the most appropriate method?
Question 10
How do you visualize JUnit test results in the CodeBuild console and send an SNS notification on failure?
Question 11
In a multi-account environment, CodeBuild tries to read a Secrets Manager secret in Account B. CodeBuild is in Account A. What two configurations are required?
Question 12
You want to generate an SBOM (Software Bill of Materials) in a CodeBuild build to strengthen supply chain security. What is the most suitable implementation?