CodeBuild: Project, buildspec.yml, and the First Build
The code is now on CodeCommit. The next stage of the chain is build: take that source, run the compile/test/package steps, and produce an artifact ready to deploy. This is CodeBuild's job. This article creates a build project, writes a buildspec.yml defining the steps, runs a real build against the app pushed in the previous article, then reads the log to see exactly what CodeBuild does.
Goal
Understand how CodeBuild runs a build (not just "click build"), be able to write a buildspec.yml, create a project via the CLI, and read the result through phases and logs.
buildspec.yml: the build recipe
CodeBuild doesn't guess how to build your app — you declare it in a buildspec.yml file placed at the repo root. The docs describe it as a "collection of build commands and related settings, in YAML format, that CodeBuild uses to run a build." The structure revolves around phases that run sequentially:
version: 0.2
phases:
install:
commands:
- echo "Install phase — preparing the build environment"
pre_build:
commands:
- echo "Pre-build — validating the source"
- test -f index.html && echo "index.html present"
build:
commands:
- echo "Build phase — static site, packaging as-is"
- echo "<!-- built by CodeBuild at $(date -u) -->" >> index.html
post_build:
commands:
- echo "Post-build — build finished"
artifacts:
files:
- index.html
- appspec.yml
- 'scripts/**/*'
name: awscicd-demo-app
The four phases you declare: install (install tools/runtime), pre_build (preparation, registry login, checks), build (the main build commands), post_build (packaging, pushing images...). Our app is a static page, so the commands are just illustrative; with a real app this is where you call npm run build, mvn package, go build... The artifacts block declares which files get bundled into the output — here index.html plus the appspec.yml and scripts/ that the deploy article will add. Commit this file to the repo and push to CodeCommit (git push origin main).
Choosing the build environment
CodeBuild runs the build inside a prebuilt Docker container. AWS provides "curated" images; don't memorize the names since the list changes — ask the API directly:
$ aws codebuild list-curated-environment-images \
--query "platforms[].languages[].images[].name" --output text \
| tr '\t' '\n' | grep amazonlinux-x86_64-standard
aws/codebuild/amazonlinux-x86_64-standard:6.0
...
The latest standard at the time of writing is aws/codebuild/amazonlinux-x86_64-standard:6.0 (based on Amazon Linux 2023). This is the image we use. Tip: in the configuration you can choose "always use the latest version of the runtime" to benefit from AMI caching and shorten provisioning time.
Create the build project
A project ties three things together: source (where to get the code), environment (which image to build in), artifacts (where to push the output), plus the service role (Article 2) for permissions. Declare it with a JSON file for brevity:
{
"name": "awscicd-demo-build",
"source": {
"type": "CODECOMMIT",
"location": "https://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/awscicd-demo-app",
"buildspec": "buildspec.yml"
},
"sourceVersion": "refs/heads/main",
"artifacts": {
"type": "S3",
"location": "awscicd-artifacts-111122223333-ap-southeast-1",
"path": "builds", "packaging": "ZIP", "name": "awscicd-demo-app.zip"
},
"environment": {
"type": "LINUX_CONTAINER",
"image": "aws/codebuild/amazonlinux-x86_64-standard:6.0",
"computeType": "BUILD_GENERAL1_SMALL"
},
"serviceRole": "arn:aws:iam::111122223333:role/awscicd-codebuild-role"
}
$ aws codebuild create-project --cli-input-json file://cb-project.json \
--query 'project.[name,environment.image,serviceRole]' --output text
awscicd-demo-build aws/codebuild/amazonlinux-x86_64-standard:6.0 arn:aws:iam::111122223333:role/awscicd-codebuild-role
The project points its source at CodeCommit, its artifact at the S3 bucket from Article 2, and runs under the CodeBuild service role we created — thanks to that role it has the codecommit:GitPull permission, plus log writing and S3 writing.
Run a real build
$ BUILD_ID=$(aws codebuild start-build --project-name awscicd-demo-build --query 'build.id' --output text)
$ echo "$BUILD_ID"
awscicd-demo-build:67099cde-784b-4788-a31c-dd8004c5cae8
The build runs asynchronously; poll the status until it finishes:
$ aws codebuild batch-get-builds --ids "$BUILD_ID" \
--query 'builds[0].[buildStatus,currentPhase]' --output text
IN_PROGRESS PROVISIONING
...
SUCCEEDED COMPLETED
What a build actually involves
This is the part worth dissecting. A build isn't just "run your commands" — CodeBuild goes through a chain of phases, in which the phases you declared (install/pre_build/build/post_build) are only the middle. View them all:
$ aws codebuild batch-get-builds --ids "$BUILD_ID" \
--query 'builds[0].phases[].[phaseType,phaseStatus,durationInSeconds]' --output text
SUBMITTED SUCCEEDED 0
QUEUED SUCCEEDED 0
PROVISIONING SUCCEEDED 4
DOWNLOAD_SOURCE SUCCEEDED 5
INSTALL SUCCEEDED 0
PRE_BUILD SUCCEEDED 0
BUILD SUCCEEDED 0
POST_BUILD SUCCEEDED 0
UPLOAD_ARTIFACTS SUCCEEDED 0
FINALIZING SUCCEEDED 0
COMPLETED
PROVISIONING (4s) is when CodeBuild stands up the container from the image; DOWNLOAD_SOURCE (5s) pulls the code from CodeCommit; then come the four phases from buildspec.yml; UPLOAD_ARTIFACTS bundles the output and pushes it to S3; FINALIZING cleans up. This container is ephemeral — stood up for this build, thrown away when done. The next build stands up a fresh container, fully clean, so builds are reproducible: no leftover junk from the previous run.
CodeCommit (refs/heads/main)
│ DOWNLOAD_SOURCE
▼
┌─────── temporary container (amazonlinux-x86_64-standard:6.0) ───────┐
│ runs buildspec.yml: │
│ INSTALL → PRE_BUILD → BUILD → POST_BUILD │
└───────┬───────────────────────────────────┬────────────────────────┘
│ UPLOAD_ARTIFACTS │ each command line → log
▼ ▼
S3: builds/awscicd-demo-app.zip CloudWatch Logs
/aws/codebuild/awscicd-demo-build
Reading the log
Each command in the buildspec and its output is written to CloudWatch Logs, group /aws/codebuild/<project>. View a few lines:
$ aws logs get-log-events --log-group-name /aws/codebuild/awscicd-demo-build \
--log-stream-name 67099cde-... --query 'events[].message' --output text
...
Running command test -f index.html && echo "index.html present"
index.html present
Phase complete: PRE_BUILD State: SUCCEEDED
Entering phase BUILD
Running command echo "Build phase — static site, packaging as-is"
Build phase — static site, packaging as-is
...
Phase complete: POST_BUILD State: SUCCEEDED
The log shows each command run, its output, and the boundary of each phase. When a build fails, this is the first place to look — which phase failed, which command, what error.
The artifact is now on S3, packaged as a ZIP:
$ aws s3 ls s3://awscicd-artifacts-111122223333-ap-southeast-1/builds/ --recursive
2026-05-25 12:53:07 342 builds/awscicd-demo-app.zip
🧹 Cleanup
This project is reused by the pipeline in Part V, so keep it until the end of the series. When you want to delete it:
$ aws codebuild delete-project --name awscicd-demo-build
CodeBuild only bills for actual build minutes, with nothing running in the background, so keeping a project that isn't building costs nothing.
Wrap-up
CodeBuild builds according to the recipe in buildspec.yml: the phases install/pre_build/build/post_build run sequentially inside a temporary container stood up from a curated image (latest amazonlinux-x86_64-standard:6.0). A project ties together source (CodeCommit), environment (image + compute), and artifacts (S3), running under a service role. The build goes through a wider chain of phases than the part you declare (PROVISIONING, DOWNLOAD_SOURCE, UPLOAD_ARTIFACTS...); each command writes to CloudWatch; the artifact comes out as a ZIP on S3. The container is ephemeral, so builds are reproducible.
The next article digs deeper into CodeBuild: passing environment variables and secrets safely (from SSM Parameter Store / Secrets Manager), enabling cache to speed up builds, and tuning the artifacts block.