Secure-by-default AWS CodeBuild project — build project keystone plus its natural sub-resources (Git source credentials, test/coverage report groups, source-repo webhook), with
privileged_mode = false, optional VPC placement, and customer-managed-KMS artifact encryption, all from a single composite call. Built for the AWS provider v6.x.
- 🏗️ Provisions the
aws_codebuild_projectkeystone — source, artifacts, environment, VPC placement, caching, logging, and batch-build options — from one call. - 🔑
service_role_arnis a required input, never created here. The build service role lives interraform-aws-iam-role; this module only accepts and passes its ARN, and documents theiam:PassRolegrant the Terraform identity needs to do so. - 🔐
privileged_mode = falseby default. Docker-in-Docker / image-building builds are an explicit, documented opt-in — a privileged container can reach the host Docker socket. - 🌐 No VPC placement by default. Builds run in CodeBuild's own per-build isolated network unless
vpc_config(all three ofvpc_id/subnets/security_group_ids, enforced together by the type system) is supplied. - 🗝️ Customer-managed KMS artifact encryption, opt-in via
encryption_key_arn; falls back to the AWS-managed S3 key — CodeBuild never stores artifacts unencrypted either way. - 🔗 Git source credentials, report groups, and webhooks modeled as sibling child collections (
for_eachovermap(object(...))), matching how the provider represents them as independent, non-project-scoped resources. - 🏷️ Universal tagging where the schema allows it.
var.tagsflows to the project and to report groups;aws_codebuild_source_credentialandaws_codebuild_webhookaccept notagsargument at all in the current provider schema. - 🙈 Source-credential tokens are
sensitive = trueend to end — the variable, and the resourcefor_eachis driven bynonsensitive(keys(...))so the token values themselves never need to leave sensitive status.
💡 Why it matters: CodeBuild is the compute engine behind nearly every CI/CD pipeline at. A build project that defaults to no privileged mode, no VPC exposure, and encrypted artifacts — while still making the
iam:PassRoleand CMK-grant requirements explicit — keeps a regulated FI's build plane from becoming its softest target.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
terraform-aws-codebuild sits in the developer_tools family (Phase 5). It consumes its service role, encryption key, network placement, artifact/cache/log storage, and secret material from sibling foundation modules, and is consumed in turn by terraform-aws-codepipeline as a build/test action.
flowchart LR
iam["terraform-aws-iam-role"]
kms["terraform-aws-kms"]
vpc["terraform-aws-vpc"]
sg["terraform-aws-security-group"]
s3["terraform-aws-s3-bucket"]
cwlg["terraform-aws-cloudwatch-log-group"]
cb["terraform-aws-codebuild"]
cp["terraform-aws-codepipeline"]
iam -- "service_role_arn (iam:PassRole)" --> cb
kms -- "encryption_key_arn" --> cb
vpc -- "vpc_config.vpc_id /.subnets" --> cb
sg -- "vpc_config.security_group_ids" --> cb
s3 -- "artifacts / cache / report bucket" --> cb
cwlg -- "logs_config.cloudwatch_logs.group_name" --> cb
cb -- "arn / name" --> cp
style cb fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
flowchart TB
subgraph MOD["terraform-aws-codebuild"]
proj["aws_codebuild_project.this<br/>(keystone)"]
sc["aws_codebuild_source_credential.this<br/>for_each source_credentials"]
rg["aws_codebuild_report_group.this<br/>for_each report_groups"]
wh["aws_codebuild_webhook.this<br/>for_each webhooks (0..1)"]
end
vpcIn["vpc_config<br/>vpc_id / subnets / security_group_ids"]
kmsIn["encryption_key_arn (CMK)"]
logsIn["logs_config.cloudwatch_logs.group_name"]
rgKms["export_config.s3_destination.encryption_key_arn"]
vpcIn -. optional.-> proj
kmsIn -. optional.-> proj
logsIn -. optional.-> proj
rgKms -. required.-> rg
proj --> wh
sc -. "referenced by build_source.auth / server_type".-> proj
style proj fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
| Resource | Role | Cardinality |
|---|---|---|
aws_codebuild_project.this |
Keystone build project | 1 |
aws_codebuild_source_credential.this |
Account/Region-scoped Git host credential | per source_credentials entry |
aws_codebuild_report_group.this |
Test/code-coverage report destination | per report_groups entry |
aws_codebuild_webhook.this |
Source-repo build trigger for this project | 0 or 1 (per webhooks entry, capped) |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 (resolved v6.53.0 at authoring time) |
No provider {} block is declared inside the module — the caller's configured provider (region/credentials) is inherited. No region variable — CodeBuild has no us-east-1 global-resource constraint.
Least-privilege actions the Terraform execution identity needs to manage everything this module creates.
| Action | Required for | Notes |
|---|---|---|
codebuild:CreateProject, codebuild:UpdateProject, codebuild:DeleteProject, codebuild:BatchGetProjects |
Project lifecycle | Core CRUD |
codebuild:ImportSourceCredentials, codebuild:DeleteSourceCredentials, codebuild:ListSourceCredentials |
source_credentials lifecycle |
Account/Region-scoped, not project-scoped |
codebuild:CreateReportGroup, codebuild:UpdateReportGroup, codebuild:DeleteReportGroup, codebuild:BatchGetReportGroups |
report_groups lifecycle |
— |
codebuild:CreateWebhook, codebuild:UpdateWebhook, codebuild:DeleteWebhook |
webhooks lifecycle |
Requires prior OAuth authorization — see Prerequisites |
iam:PassRole (scoped to service_role_arn, condition iam:PassedToService = codebuild.amazonaws.com) |
MANDATORY. aws_codebuild_project.this requires service_role at creation |
codebuild:CreateProject/UpdateProject fail with an access-denied error if the Terraform principal cannot pass this exact role, even when codebuild:* is otherwise fully granted |
iam:PassRole (scoped to build_batch_config.service_role_arn) |
Batch-build orchestration role | Only when build_batch_config is set — a second, distinct role/ARN from service_role_arn |
kms:DescribeKey |
Validating encryption_key_arn / report_groups[*].export_config.s3_destination.encryption_key_arn at plan/apply time |
The CMK's grant to the build role is a separate, out-of-band key-policy statement |
ec2:DescribeSubnets, ec2:DescribeSecurityGroups, ec2:DescribeVpcs |
Validating vpc_config |
VPC-connected builds |
s3:GetBucketLocation |
Validating S3 artifact/cache/log/report bucket references | Read-only; this module never writes a bucket policy |
⚠️ iam:PassRolefailures look likecodebuild:CreateProjectfailures. If project creation is denied, checkiam:PassRoleonservice_role_arn(andbuild_batch_config.service_role_arn, if set) before assuming a missing CodeBuild permission.ℹ️ This table is the Terraform identity's permissions — not the CodeBuild service role's runtime permissions. The service role (owned by
terraform-aws-iam-role) needs its own least-privilege policy:logs:CreateLogGroup/CreateLogStream/PutLogEvents, S3 read/write on the artifact/source buckets,ec2:CreateNetworkInterface+ related ENI actions for VPC builds, andkms:GenerateDataKey*/kms:Decrypton the CMK when one is supplied. Author that policy alongside the role, not here.
- No service-linked role is created by CodeBuild for any resource in this module.
- VPC-connected builds need ENI permissions on the SERVICE role, not the Terraform identity:
ec2:CreateNetworkInterface,ec2:DescribeDhcpOptions,ec2:DescribeNetworkInterfaces,ec2:DeleteNetworkInterface,ec2:DescribeSubnets,ec2:DescribeSecurityGroups,ec2:DescribeVpcs, and a scopedec2:CreateNetworkInterfacePermission(condition onec2:Subnet+ec2:AuthorizedService = codebuild.amazonaws.com). Without these the build starts and fails immediately with an ENI-creation error — this module cannot grant them; they must already be onservice_role_arn. - GitHub/GitHub Enterprise/Bitbucket OAuth-based source access (
build_source.auth.type = "CODECONNECTIONS", or a legacy OAuth "GitHub App" connection) must be manually authorized once per account/Region in the CodeBuild console before Terraform can reference the resulting connection ARN. This is an interactive, non-API step.PERSONAL_ACCESS_TOKEN/BASIC_AUTHviasource_credentialsavoids the manual step but pins a long-lived token as a sensitive Terraform input. - One source credential per server type per Region. CodeBuild allows only a single
aws_codebuild_source_credentialperserver_typeper account per Region — importing a second overwrites the first. Terraform cannot see credentials created outside this module's state, so this is a documented constraint, not an enforced count. - Report groups are account/Region-scoped, not project children — a report group can be shared across projects. Deleting one that still holds stored reports requires
delete_reports = trueor a prior manual report deletion. - Webhooks auto-manage the remote repo hook for GitHub/Bitbucket only. For GitHub Enterprise, the repository-side webhook must be created and managed separately (e.g. the
github_repository_webhookresource) using this module'swebhook_payload_urls/webhook_secretoutputs. CodeBuild must already be OAuth-authorized against the source provider in that Region, or webhook creation returnsResourceNotFoundException: Could not find access token for server type github. - Quotas: concurrent-build limit is Region/account-specific (soft, raise via Service Quotas);
build_timeoutmax is 2160 minutes (36 hours) andqueued_timeoutmax is 480 minutes (8 hours) — neither applies to theLambdacompute-type family. - No
regionvariable — CodeBuild is fully regional with nous-east-1global-resource constraint (unlike CloudFront/WAFv2/ACM).
terraform-aws-codebuild/
├── providers.tf # terraform{} + required_providers (aws >= 6.0, < 7.0); no provider block
├── variables.tf # name, service_role_arn, build_source, artifacts, environment, vpc_config,...
├── main.tf # aws_codebuild_project.this + source_credential / report_group / webhook collections
├── outputs.tf # id + arn, name, badge/alias, child ARN maps, tags_all
├── README.md # this file
└── SCOPE.md # in/out-of-scope, IAM, prerequisites, emits, gotchas
module "build_role" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
name = "casey-app-codebuild"
assume_role_policy = data.aws_iam_policy_document.codebuild_trust.json
#... least-privilege inline policy: CloudWatch Logs + S3 artifact bucket
}
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-app-build"
service_role_arn = module.build_role.arn
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "NO_ARTIFACTS" # test-only project; see Example Library for S3 artifacts
}
environment = {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
tags = {
Environment = "prod"
}
}
⚠️ Pin the source with?ref=v1.0.0— never a branch. The Terraform identity applying this module must be able toiam:PassRolemodule.build_role.arntocodebuild.amazonaws.com— see Required IAM Permissions.
| Input | Type | Source module |
|---|---|---|
service_role_arn |
string (ARN) |
terraform-aws-iam-role |
build_batch_config.service_role_arn |
string (ARN), optional |
terraform-aws-iam-role (batch orchestration role) |
encryption_key_arn |
string (ARN), optional |
terraform-aws-kms |
report_groups[*].export_config.s3_destination.encryption_key_arn |
string (ARN) |
terraform-aws-kms |
vpc_config.vpc_id / .subnets |
string / list(string), optional |
terraform-aws-vpc |
vpc_config.security_group_ids |
list(string), optional |
terraform-aws-security-group |
| Artifact / cache / log / report bucket name/ARN | string, optional (free-form location fields) |
terraform-aws-s3-bucket |
logs_config.cloudwatch_logs.group_name |
string, optional |
terraform-aws-cloudwatch-log-group |
build_source.auth.resource (SECRETS_MANAGER) / environment.registry_credential.credential |
string (ARN), optional |
terraform-aws-secrets-manager |
build_source.auth.resource (CODECONNECTIONS) |
string (ARN), optional |
manually provisioned CodeConnections/CodeStar connection (out of Terraform) |
| Output | Description | Consumed by |
|---|---|---|
id |
Project name or ARN (per how it was created/imported) | Reference wiring into terraform-aws-codepipeline |
arn |
Project ARN — cross-resource reference type | terraform-aws-codepipeline, terraform-aws-iam-policy |
name |
Project name | terraform-aws-codepipeline, monitoring/alarm modules |
badge_url |
Public build-badge URL (badge_enabled = true only) |
READMEs / status badges |
public_project_alias |
Public build-API identifier (project_visibility = "PUBLIC_READ" only) |
Public build API callers |
source_credential_arns |
Map of caller key → source-credential ARN | Audit/cross-reference |
report_group_arns / report_group_ids |
Map of caller key → report-group ARN/id | terraform-aws-codepipeline test/coverage reporting |
webhook_urls / webhook_payload_urls |
Map of caller key → webhook URL / payload URL | Manual GHE webhook wiring |
webhook_secret |
Map of caller key → webhook secret — sensitive | GHE manual wiring only |
tags_all |
All tags incl. provider default_tags |
governance/audit |
1 · Minimal — GitHub source, no artifacts
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-lint"
service_role_arn = module.build_role.arn
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "NO_ARTIFACTS"
}
environment = {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
}2 · S3 build artifacts, customer-managed KMS encryption
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-app-build"
service_role_arn = module.build_role.arn
encryption_key_arn = module.kms.arn # terraform-aws-kms — CMK, never AWS-managed for prod artifacts
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "S3"
location = module.artifact_bucket.name # terraform-aws-s3-bucket
packaging = "ZIP"
}
environment = {
compute_type = "BUILD_GENERAL1_MEDIUM"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
}3 · VPC-connected build (private CodeArtifact / internal registry access)
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-vpc-build"
service_role_arn = module.build_role.arn # must carry ENI permissions — see AWS Prerequisites
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "NO_ARTIFACTS"
}
environment = {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
vpc_config = {
vpc_id = module.vpc.vpc_id # terraform-aws-vpc
subnets = values(module.vpc.private_subnet_ids) # private tier — no NAT dependency required for internal-only reach
security_group_ids = [module.codebuild_sg.id] # terraform-aws-security-group
}
}4 · Privileged mode opt-out — Docker image build/push
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-image-build"
service_role_arn = module.build_role.arn # needs ECR push permissions
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "NO_ARTIFACTS"
}
environment = {
compute_type = "BUILD_GENERAL1_MEDIUM"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
privileged_mode = true # OPT-OUT of the secure default — required for `docker build`/`docker push`
}
}5 · Source credential — PAT-based GitHub auth (sensitive token)
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-private-repo-build"
service_role_arn = module.build_role.arn
source_credentials = {
github = {
auth_type = "PERSONAL_ACCESS_TOKEN"
server_type = "GITHUB"
token = var.github_pat # mark the CALLER'S variable sensitive too
}
}
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/private-app.git"
}
artifacts = {
type = "NO_ARTIFACTS"
}
environment = {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
}
⚠️ AWS never returnstokenon read — Terraform cannot detect out-of-band rotation. A rotated token requires a freshapplywith the new value.
6 · Report group — code coverage, KMS-encrypted S3 export
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-app-build"
service_role_arn = module.build_role.arn
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "NO_ARTIFACTS"
}
environment = {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
report_groups = {
coverage = {
type = "CODE_COVERAGE"
export_config = {
type = "S3"
s3_destination = {
bucket = module.report_bucket.name # terraform-aws-s3-bucket
encryption_key_arn = module.kms.arn # terraform-aws-kms — mandatory, no AWS-managed fallback
packaging = "ZIP"
}
}
}
}
}7 · Report group — NO_EXPORT (still requires an s3_destination block)
report_groups = {
smoke_tests = {
type = "TEST"
export_config = {
type = "NO_EXPORT"
# The live provider schema requires s3_destination even for NO_EXPORT —
# supply a placeholder bucket/key; CodeBuild does not export to it.
s3_destination = {
bucket = module.report_bucket.name
encryption_key_arn = module.kms.arn
}
}
}
}8 · Webhook — build on push to main, PR filters
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-app-build"
service_role_arn = module.build_role.arn
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "NO_ARTIFACTS"
}
environment = {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
webhooks = {
on_push_and_pr = {
build_type = "BUILD"
filter_groups = [
{ filters = [{ type = "EVENT", pattern = "PUSH" }, { type = "BASE_REF", pattern = "^refs/heads/main$" }] },
{ filters = [{ type = "EVENT", pattern = "PULL_REQUEST_CREATED,PULL_REQUEST_UPDATED,PULL_REQUEST_REOPENED" }] },
]
}
}
}ℹ️ Requires CodeBuild to already be OAuth-authorized against GitHub in this Region — see AWS Prerequisites.
9 · Tags — project + report group only (source credentials/webhooks accept none)
# Caller's provider block owns default_tags; resource tags win on key conflict.
provider "aws" {
default_tags {
tags = { Owner = "platform", ManagedBy = "terraform" }
}
}
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-tagged-build"
service_role_arn = module.build_role.arn
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "NO_ARTIFACTS"
}
environment = {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
report_groups = {
coverage = {
type = "CODE_COVERAGE"
export_config = {
type = "S3"
s3_destination = { bucket = module.report_bucket.name, encryption_key_arn = module.kms.arn }
}
tags = { ReportOwner = "qa-team" } # merges over module tags for THIS report group only
}
}
tags = {
Environment = "prod" # applies to the project AND the coverage report group above
}
}
# module.codebuild.tags_all == { Owner="platform", ManagedBy="terraform", Environment="prod" }
# aws_codebuild_source_credential / aws_codebuild_webhook carry NO tags at all — not a gap, a schema limit.10 · Secure-by-default opt-out — encryption_disabled on S3 artifacts
artifacts = {
type = "S3"
location = module.artifact_bucket.name
encryption_disabled = true # OPT-OUT — document the exception; artifacts land unencrypted at rest
}11 · Batch builds — dedicated orchestration role
module "batch_role" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
name = "casey-codebuild-batch-orchestrator"
assume_role_policy = data.aws_iam_policy_document.codebuild_trust.json
}
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-batch-build"
service_role_arn = module.build_role.arn
build_source = { type = "GITHUB", location = "https://github.com/microsoftexpert/app.git" }
artifacts = { type = "NO_ARTIFACTS" }
environment = { compute_type = "BUILD_GENERAL1_SMALL", image = "aws/codebuild/standard:7.0", type = "LINUX_CONTAINER" }
build_batch_config = {
service_role_arn = module.batch_role.arn # a SECOND, distinct iam:PassRole grant
combine_artifacts = true
restrictions = {
maximum_builds_allowed = 10
}
}
}12 · Registry credential — private Docker image via Secrets Manager
environment = {
compute_type = "BUILD_GENERAL1_SMALL"
image = "137112412989.dkr.ecr.us-east-1.amazonaws.com/casey/build-image:latest"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "SERVICE_ROLE" # required for cross-account/private registry images
registry_credential = {
credential = module.registry_secret.arn # terraform-aws-secrets-manager
credential_provider = "SECRETS_MANAGER"
}
}13 · Custom CloudWatch log group + S3 log export
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-observed-build"
service_role_arn = module.build_role.arn
build_source = { type = "GITHUB", location = "https://github.com/microsoftexpert/app.git" }
artifacts = { type = "NO_ARTIFACTS" }
environment = { compute_type = "BUILD_GENERAL1_SMALL", image = "aws/codebuild/standard:7.0", type = "LINUX_CONTAINER" }
logs_config = {
cloudwatch_logs = {
group_name = module.build_log_group.name # terraform-aws-cloudwatch-log-group
}
s3_logs = {
status = "ENABLED"
location = "${module.log_bucket.name}/codebuild"
}
}
}14 · S3 build cache (faster incremental builds)
cache = {
type = "S3"
location = module.cache_bucket.name # terraform-aws-s3-bucket
}15 · End-to-end composition — full CI stack (finale)
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
name = "casey-codebuild"
}
module "artifact_bucket" {
source = "git::https://github.com/microsoftexpert/terraform-aws-s3-bucket?ref=v1.0.0"
bucket = "casey-app-build-artifacts"
kms_key_arn = module.kms.arn
}
module "vpc" {
source = "git::https://github.com/microsoftexpert/terraform-aws-vpc?ref=v1.0.0"
name = "casey-ci"
cidr_block = "10.60.0.0/16"
private_subnets = {
a = { availability_zone = "us-east-1a", cidr_block = "10.60.10.0/24" }
}
}
module "codebuild_sg" {
source = "git::https://github.com/microsoftexpert/terraform-aws-security-group?ref=v1.0.0"
name = "casey-codebuild"
vpc_id = module.vpc.vpc_id
}
module "build_role" {
source = "git::https://github.com/microsoftexpert/terraform-aws-iam-role?ref=v1.0.0"
name = "casey-app-codebuild"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "codebuild.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
inline_policies = {
build-permissions = {
policy = data.aws_iam_policy_document.build_role_policy.json # logs, S3, ENI, kms:GenerateDataKey*/Decrypt
}
}
}
module "codebuild" {
source = "git::https://github.com/microsoftexpert/terraform-aws-codebuild?ref=v1.0.0"
name = "casey-app-build"
service_role_arn = module.build_role.arn
encryption_key_arn = module.kms.arn
build_source = {
type = "GITHUB"
location = "https://github.com/microsoftexpert/app.git"
}
artifacts = {
type = "S3"
location = module.artifact_bucket.name
packaging = "ZIP"
}
environment = {
compute_type = "BUILD_GENERAL1_MEDIUM"
image = "aws/codebuild/standard:7.0"
type = "LINUX_CONTAINER"
}
vpc_config = {
vpc_id = module.vpc.vpc_id
subnets = values(module.vpc.private_subnet_ids)
security_group_ids = [module.codebuild_sg.id]
}
report_groups = {
coverage = {
type = "CODE_COVERAGE"
export_config = {
type = "S3"
s3_destination = { bucket = module.artifact_bucket.name, encryption_key_arn = module.kms.arn, path = "coverage" }
}
}
}
webhooks = {
on_push = {
build_type = "BUILD"
filter_groups = [{ filters = [{ type = "EVENT", pattern = "PUSH" }] }]
}
}
tags = { Environment = "prod", App = "lending-portal" }
}
# Downstream: terraform-aws-codepipeline wires module.codebuild.name/.arn as a build action.ℹ️ High-level grouping:
- Core (required):
name,service_role_arn,build_source,artifacts,environment - Project options:
description,build_timeout,queued_timeout,auto_retry_limit,concurrent_build_limit,badge_enabled,project_visibility,resource_access_role_arn,source_version,encryption_key_arn - Network placement:
vpc_config(object, defaultnull—vpc_id/subnets/security_group_idsall required together) - Build performance:
cache,file_system_locations - Logging:
logs_config - Batch builds:
build_batch_config - Multi-source/artifact:
secondary_sources,secondary_source_version,secondary_artifacts - Child collections:
source_credentials(sensitive),report_groups,webhooks - Universal:
tags
- Primary:
id,arn - Project attributes:
name,badge_url(null unlessbadge_enabled = true),public_project_alias(null unlessPUBLIC_READ) - Source credentials:
source_credential_arns(map) - Report groups:
report_group_arns,report_group_ids(maps) - Webhook:
webhook_urls,webhook_payload_urls(maps),webhook_secret(map,sensitive = true) - Tags:
tags_all
- ARN / ID formats:
- Project
arn→arn:aws:codebuild:<region>:<account>:project/<name>;idis the name if created/imported by name, or the ARN if created via Terraform/imported by ARN. - Source credential
idandarnare both the credential ARN (arn:aws:codebuild:<region>:<account>:token/<server-type>shape). - Report group
idandarnare both the report-group ARN. - Webhook
idis the project name — there is no separate webhook ARN. - Force-new / immutable fields:
name(no rename API);vpc_config.vpc_id(changing forces a VPC-config replace cycle in practice);build_source_credential.server_type/auth_typeare effectively immutable per credential (each server_type is a singleton per Region). tags↔tags_all↔default_tags:var.tagsflows toaws_codebuild_project.thisand toaws_codebuild_report_group(with each entry's owntagsmerged on top);aws_codebuild_source_credentialandaws_codebuild_webhookaccept notagsargument at all in the current provider schema.tags_allis surfaced from the project only.- Eventual consistency: a newly created/updated
service_role_arnmay not be immediately assumable by CodeBuild — the first build after a role change can transiently fail with an access-denied error and succeed on retry. - Destroy ordering: delete the project (and its
aws_codebuild_webhook) before deleting anaws_codebuild_source_credentialstill referenced bybuild_source.auth, and before deleting the service role. CodeBuild does not hard-block on these, but a dangling reference produces confusingBatchGetProjectsresults on the next plan. - No
us-east-1global-resource constraint — CodeBuild is fully regional. sourceis a Terraform-reserved variable name — this module's primary-source variable is namedbuild_source; it renders the provider'ssource {}block unchanged.export_config.s3_destinationis unconditionally required onreport_groups, per the live v6.53.0 provider schema — evenNO_EXPORTreport groups must supply a (functionally unused)bucket/encryption_key_arnpair.
Secure-by-default posture and the explicit opt-out for each:
| Hardened default | Behavior | Opt-out / control |
|---|---|---|
| Build artifact encryption | Customer-managed KMS key when encryption_key_arn is supplied; AWS-managed S3 key (aws/s3) otherwise — never unencrypted |
artifacts.encryption_disabled = true (S3 artifacts only) |
| Docker-in-Docker / privileged builds | environment.privileged_mode = false |
environment.privileged_mode = true (image-building workloads only — elevated blast radius, host Docker socket access) |
| Network placement | No vpc_config — builds run in CodeBuild's own isolated, no-inbound-exposure network |
Supply vpc_config (all three fields together) for private-VPC connectivity |
| Report-group export encryption | encryption_disabled = false; a KMS key is a required input, never optional-off |
None — the underlying API does not currently allow disabling report encryption |
| Source-credential tokens | sensitive = true on the variable; for_each driven by nonsensitive(keys(...)) so token values never need to be unmarked |
Not an opt-out — tokens are never safe to display in plan/state output |
| CloudWatch Logs | ON by default even with logs_config = null (CodeBuild's own default) |
logs_config.cloudwatch_logs.status = "DISABLED" |
| Image pull credentials | image_pull_credentials_type = "CODEBUILD" (no cross-account registry access implied) |
"SERVICE_ROLE" for private/cross-account ECR images |
| Project visibility | "PRIVATE" |
"PUBLIC_READ" (paired with resource_access_role_arn) |
Other principles: service_role_arn has no default — CodeBuild categorically cannot create a project without one, so defaulting to null would only defer a clearer error to apply time. This module creates no S3 buckets, log groups, KMS keys, or IAM roles/policies — all are owned by sibling modules and wired in by ARN/name.
terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan # requires valid AWS credentials (profile / SSO / OIDC) + a region
terraform apply
terraform output
⚠️ plan/applyrequire valid AWS credentials and a configured region. Always pin the module source with?ref=v1.0.0, never a branch.
terraform init -backend=false && terraform validate— schema + reference integrity.terraform fmt -check— formatting.terraform planagainst a sandbox account — confirmiam:PassRolesucceeds before assuming a CodeBuild-permission gap.- If using
vpc_config, confirm the service role's ENI permissions are in place before the first build (a plan-clean apply can still fail at build-run time with an ENI-creation error). - If using
webhooks, confirm OAuth authorization exists in the target Region beforeapply— otherwisecodebuild:CreateWebhookfails withResourceNotFoundException.
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
arn = "arn:aws:codebuild:us-east-1:123456789012:project/casey-app-build"
id = "arn:aws:codebuild:us-east-1:123456789012:project/casey-app-build"
name = "casey-app-build"
badge_url = null
report_group_arns = { "coverage" = "arn:aws:codebuild:us-east-1:123456789012:report-group/coverage" }
webhook_urls = {}
tags_all = { "Environment" = "prod", "App" = "lending-portal", "ManagedBy" = "terraform" }
AccessDeniedExceptiononCreateProject/UpdateProjecteven thoughcodebuild:CreateProjectis granted: Missingiam:PassRoleonservice_role_arn(orbuild_batch_config.service_role_arn) for the Terraform identity. This is the single most common first-apply failure for this module — checkiam:PassRolebefore anything else.- Tag drift / unexpected tags:
default_tagsoverlap.tags_all(project only) merges resource tags over providerdefault_tags, resource tags winning — set the value explicitly invar.tagsif it looks wrong. Rememberaws_codebuild_source_credential/aws_codebuild_webhookcarry no tags at all. - Credential-chain failures (
NoCredentialProviders/ExpiredToken): No valid AWS credentials resolved. SetAWS_PROFILE, refresh SSO, or confirm OIDC role assumption in CI — this module never takes credentials as variables. - Build starts then immediately fails with an ENI-creation error:
vpc_configis set butservice_role_arnlacks ENI permissions (ec2:CreateNetworkInterfaceand related actions) — this module cannot grant them; add them to the service role's policy. ResourceNotFoundException: Could not find access token for server type githubon webhook/project apply: CodeBuild has not been OAuth-authorized against that source provider in this Region — a manual console step, not something Terraform can perform.- Docker build fails at the
docker buildstep, not atplan/apply:environment.privileged_modeisfalse(secure default). Set ittrueonly for workloads that genuinely build/push images. - Rotated a source-credential token but Terraform shows no diff: Expected — AWS never returns
tokenon read, so Terraform cannot detect out-of-band rotation. Change the variable value and re-apply. aws_codebuild_webhook.secretisnullor stale: The secret is only populated at creation for the GHE manual-wiring path and is not returned for every source type; a rotated secret requires tainting and recreating the webhook resource.- Second
source_credentialsentry for the sameserver_typesilently replaces the first: CodeBuild allows only one credential perserver_typeper account per Region — this is an AWS-side limit this module cannot enforce across separateterraform applyruns/states.
- Terraform Registry —
hashicorp/awsprovider:aws_codebuild_project,aws_codebuild_source_credential,aws_codebuild_report_group,aws_codebuild_webhook - AWS — Allow Amazon VPC access in your CodeBuild projects (User Guide)
- AWS — Encrypt build outputs using a customer managed key (User Guide)
- AWS — Create test reports / Report groups (User Guide)
- AWS — Connect Bitbucket/GitHub using OAuth (console) and Create and store a token in a Secrets Manager secret (User Guide)
- —
terraform-aws-iam-role,terraform-aws-kms,terraform-aws-vpc,terraform-aws-security-group,terraform-aws-s3-bucket,terraform-aws-cloudwatch-log-group,terraform-aws-secrets-manager,terraform-aws-codepipeline(sibling modules)
🧡 "Infrastructure as Code should be standardized, consistent, and secure."