CodeBuild Advanced: Environment Variables, Secrets, and Cache
The build in the previous article just printed a few lines. A real build needs more: an environment-specific API URL, a key to call a service, a password to run a database migration. The question is how to pass those values into a build safely — not hardcoded into source, not printed to logs. This article does exactly that, drawing a clear line between plain variables and secrets, then enables cache for faster builds.
Goal
Pass environment variables and secrets into CodeBuild the right way (plain variables vs SSM vs Secrets Manager), see how CodeBuild masks secrets in the logs, and enable cache.
Three sources of environment variables
The env block in buildspec.yml takes values from three sources, each for a purpose:
env:
variables:
APP_ENV: "production"
parameter-store:
GREETING: /cicddemo/greeting
API_KEY: /cicddemo/api-key
secrets-manager:
DB_PASSWORD: awscicd/demo/db:password
variables are plain values written straight into the buildspec — used for config that is not sensitive (environment, feature flags). parameter-store pulls values from SSM Parameter Store by parameter name. secrets-manager pulls from Secrets Manager, with the syntax secret-name:json-key. The core difference: the latter two sources keep the value outside the source code; the buildspec only records the reference name, and CodeBuild reads the real value at build time.
Create the parameters and secret
SSM Parameter Store — one plain parameter and one SecureString (encrypted):
$ aws ssm put-parameter --name /cicddemo/greeting --value "hello-from-ssm" --type String
$ aws ssm put-parameter --name /cicddemo/api-key --value "s3cr3t-ssm-value" --type SecureString
(Note: SSM parameter names must not start with aws — that is a reserved prefix and will be rejected; use a different name like /cicddemo/.)
Secrets Manager — a JSON secret:
$ aws secretsmanager create-secret --name awscicd/demo/db \
--secret-string '{"password":"p@ss-from-secretsmanager"}'
The CodeBuild service role (Article 2) does not yet have permission to read these two, so grant it — scoped exactly to our paths, not broader:
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": ["ssm:GetParameters","ssm:GetParameter"],
"Resource": "arn:aws:ssm:*:*:parameter/cicddemo/*" },
{ "Effect": "Allow", "Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:*:*:secret:awscicd/*" }
]
}
$ aws iam put-role-policy --role-name awscicd-codebuild-role \
--policy-name awscicd-codebuild-secrets --policy-document file://cb-secrets.json
CodeBuild masks secrets in the logs automatically
This is the most important point of the article. Edit the buildspec to print the variables out (deliberately, to see them), then run a build and read the logs:
$ aws logs get-log-events --log-group-name /aws/codebuild/awscicd-demo-build ...
Install phase — APP_ENV=production
GREETING from SSM = ***
API_KEY present? yes
DB_PASSWORD present? yes
Read each line carefully. APP_ENV=production shows in full — it is a variables entry, not sensitive. But GREETING (pulled from Parameter Store) is replaced with ***, even though its value is just the harmless "hello-from-ssm". CodeBuild automatically masks every value coming from parameter-store and secrets-manager in the logs, regardless of whether it is actually secret. As for API_KEY and DB_PASSWORD, we only print "present? yes" so as not to expose them, and they were injected into the environment exactly as expected.
The practical takeaway: put a secret in parameter-store/secrets-manager and CodeBuild masks it for you; put a secret in variables and it shows in full in the logs and lives in the source code — never do that.
buildspec env:
variables: APP_ENV=production ──────────▶ shown in log
parameter-store: GREETING = /cicddemo/... ──┐
secrets-manager: DB_PASSWORD = awscicd/... ──┤ CodeBuild reads at build time
▼
injected into env var, value MASKED (***) in log
Enable cache for faster builds
Each build spins up a fresh, clean container — good for reproducibility but slow if it has to re-download dependencies every time. Cache keeps some things between builds. Enable LOCAL cache for the project:
$ aws codebuild update-project --name awscicd-demo-build \
--cache '{"type":"LOCAL","modes":["LOCAL_SOURCE_CACHE","LOCAL_CUSTOM_CACHE"]}' \
--query 'project.cache.type' --output text
LOCAL
There are three LOCAL cache modes: LOCAL_SOURCE_CACHE keeps git metadata (faster clone next time), LOCAL_DOCKER_LAYER_CACHE keeps Docker layers (for image builds), LOCAL_CUSTOM_CACHE keeps the directories you declare in the buildspec's cache.paths (e.g. node_modules, ~/.m2). LOCAL cache is tied to the machine running the build, so it only helps when the same machine is reused; for cache that persists across all builds, use type: S3 pointing to a bucket. For apps with many dependencies, cache cuts most of the install time.
🧹 Cleanup
The SSM params and secret are kept until the end of the series (builds in later articles still reference them). When cleaning up:
$ aws ssm delete-parameter --name /cicddemo/greeting
$ aws ssm delete-parameter --name /cicddemo/api-key
$ aws secretsmanager delete-secret --secret-id awscicd/demo/db --force-delete-without-recovery
SSM Parameter Store (standard) is free; Secrets Manager costs roughly USD 0.40/secret/month prorated, so keeping it a few days is only a few cents. --force-delete-without-recovery deletes immediately instead of waiting out the recovery window.
Wrap-up
CodeBuild takes environment variables from three sources: variables for plain config (shown in logs), parameter-store (SSM) and secrets-manager for sensitive values — the latter two keep the value outside source and are auto-masked (***) in the logs by CodeBuild. Grant the role read permission following least-privilege, scoped exactly to the parameter paths. Cache (LOCAL with source/docker-layer/custom modes, or S3) keeps dependencies between builds for faster runs. The golden rule: a secret never lives in variables or source.
The next article closes Part II: having CodeBuild run tests and emit a test report — collecting test results (JUnit, etc.) into a report viewable in CodeBuild, so you know the build not only runs but is also correct.