Skip to content

Repository files navigation

🟧 AWS CodeBuild Terraform Module

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.

Terraformawsmoduletyperesources


🧩 Overview

  • 🏗️ Provisions the aws_codebuild_project keystone — source, artifacts, environment, VPC placement, caching, logging, and batch-build options — from one call.
  • 🔑 service_role_arn is a required input, never created here. The build service role lives in terraform-aws-iam-role; this module only accepts and passes its ARN, and documents the iam:PassRole grant the Terraform identity needs to do so.
  • 🔐 privileged_mode = false by 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 of vpc_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_each over map(object(...))), matching how the provider represents them as independent, non-project-scoped resources.
  • 🏷️ Universal tagging where the schema allows it.var.tags flows to the project and to report groups; aws_codebuild_source_credential and aws_codebuild_webhook accept no tags argument at all in the current provider schema.
  • 🙈 Source-credential tokens are sensitive = true end to end — the variable, and the resource for_each is driven by nonsensitive(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:PassRole and CMK-grant requirements explicit — keeps a regulated FI's build plane from becoming its softest target.


❤️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

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!


🗺️ Where this fits in the family

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
Loading

🧬 What this module builds

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
Loading
ResourceRoleCardinality
aws_codebuild_project.thisKeystone build project1
aws_codebuild_source_credential.thisAccount/Region-scoped Git host credentialper source_credentials entry
aws_codebuild_report_group.thisTest/code-coverage report destinationper report_groups entry
aws_codebuild_webhook.thisSource-repo build trigger for this project0 or 1 (per webhooks entry, capped)

✅ Provider / Versions

RequirementVersion
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.


🔑 Required IAM Permissions

Least-privilege actions the Terraform execution identity needs to manage everything this module creates.

ActionRequired forNotes
codebuild:CreateProject, codebuild:UpdateProject, codebuild:DeleteProject, codebuild:BatchGetProjectsProject lifecycleCore CRUD
codebuild:ImportSourceCredentials, codebuild:DeleteSourceCredentials, codebuild:ListSourceCredentialssource_credentials lifecycleAccount/Region-scoped, not project-scoped
codebuild:CreateReportGroup, codebuild:UpdateReportGroup, codebuild:DeleteReportGroup, codebuild:BatchGetReportGroupsreport_groups lifecycle
codebuild:CreateWebhook, codebuild:UpdateWebhook, codebuild:DeleteWebhookwebhooks lifecycleRequires 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 creationcodebuild: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 roleOnly when build_batch_config is set — a second, distinct role/ARN from service_role_arn
kms:DescribeKeyValidating encryption_key_arn / report_groups[*].export_config.s3_destination.encryption_key_arn at plan/apply timeThe CMK's grant to the build role is a separate, out-of-band key-policy statement
ec2:DescribeSubnets, ec2:DescribeSecurityGroups, ec2:DescribeVpcsValidating vpc_configVPC-connected builds
s3:GetBucketLocationValidating S3 artifact/cache/log/report bucket referencesRead-only; this module never writes a bucket policy

⚠️iam:PassRole failures look like codebuild:CreateProject failures. If project creation is denied, check iam:PassRole on service_role_arn (and build_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, and kms:GenerateDataKey*/kms:Decrypt on the CMK when one is supplied. Author that policy alongside the role, not here.


📋 AWS Prerequisites

  • 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 scoped ec2:CreateNetworkInterfacePermission (condition on ec2: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 on service_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_AUTH via source_credentials avoids 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_credential per server_type per 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 = true or 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_webhook resource) using this module's webhook_payload_urls / webhook_secret outputs. CodeBuild must already be OAuth-authorized against the source provider in that Region, or webhook creation returns ResourceNotFoundException: Could not find access token for server type github.
  • Quotas: concurrent-build limit is Region/account-specific (soft, raise via Service Quotas); build_timeout max is 2160 minutes (36 hours) and queued_timeout max is 480 minutes (8 hours) — neither applies to the Lambda compute-type family.
  • No region variable — CodeBuild is fully regional with no us-east-1 global-resource constraint (unlike CloudFront/WAFv2/ACM).

📁 Module Structure

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

⚙️ Quick Start

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.arnbuild_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 to iam:PassRolemodule.build_role.arn to codebuild.amazonaws.com — see Required IAM Permissions.


🔌 Cross-Module Contract

Consumes

InputTypeSource module
service_role_arnstring (ARN)terraform-aws-iam-role
build_batch_config.service_role_arnstring (ARN), optionalterraform-aws-iam-role (batch orchestration role)
encryption_key_arnstring (ARN), optionalterraform-aws-kms
report_groups[*].export_config.s3_destination.encryption_key_arnstring (ARN)terraform-aws-kms
vpc_config.vpc_id / .subnetsstring / list(string), optionalterraform-aws-vpc
vpc_config.security_group_idslist(string), optionalterraform-aws-security-group
Artifact / cache / log / report bucket name/ARNstring, optional (free-form location fields)terraform-aws-s3-bucket
logs_config.cloudwatch_logs.group_namestring, optionalterraform-aws-cloudwatch-log-group
build_source.auth.resource (SECRETS_MANAGER) / environment.registry_credential.credentialstring (ARN), optionalterraform-aws-secrets-manager
build_source.auth.resource (CODECONNECTIONS)string (ARN), optionalmanually provisioned CodeConnections/CodeStar connection (out of Terraform)

Emits

OutputDescriptionConsumed by
idProject name or ARN (per how it was created/imported)Reference wiring into terraform-aws-codepipeline
arnProject ARN — cross-resource reference typeterraform-aws-codepipeline, terraform-aws-iam-policy
nameProject nameterraform-aws-codepipeline, monitoring/alarm modules
badge_urlPublic build-badge URL (badge_enabled = true only)READMEs / status badges
public_project_aliasPublic build-API identifier (project_visibility = "PUBLIC_READ" only)Public build API callers
source_credential_arnsMap of caller key → source-credential ARNAudit/cross-reference
report_group_arns / report_group_idsMap of caller key → report-group ARN/idterraform-aws-codepipeline test/coverage reporting
webhook_urls / webhook_payload_urlsMap of caller key → webhook URL / payload URLManual GHE webhook wiring
webhook_secretMap of caller key → webhook secret — sensitiveGHE manual wiring only
tags_allAll tags incl. provider default_tagsgovernance/audit

📚 Example Library

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.arnbuild_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.arnencryption_key_arn=module.kms.arn# terraform-aws-kms — CMK, never AWS-managed for prod artifactsbuild_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 Prerequisitesbuild_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 permissionsbuild_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.arnsource_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 returns token on read — Terraform cannot detect out-of-band rotation. A rotated token requires a fresh apply with 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.arnbuild_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.arnbuild_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.arnbuild_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.arnbuild_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.arnbuild_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.arnencryption_key_arn=module.kms.arnbuild_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.

📥 Inputs

ℹ️ 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, default nullvpc_id/subnets/security_group_ids all 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

🧾 Outputs

  • Primary:id, arn
  • Project attributes:name, badge_url(null unless badge_enabled = true), public_project_alias(null unless PUBLIC_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

🧠 Architecture Notes

  • ARN / ID formats:
  • Project arnarn:aws:codebuild:<region>:<account>:project/<name>; id is the name if created/imported by name, or the ARN if created via Terraform/imported by ARN.
  • Source credential id and arn are both the credential ARN (arn:aws:codebuild:<region>:<account>:token/<server-type> shape).
  • Report group id and arn are both the report-group ARN.
  • Webhook id is 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_type are effectively immutable per credential (each server_type is a singleton per Region).
  • tagstags_alldefault_tags:var.tags flows to aws_codebuild_project.this and to aws_codebuild_report_group (with each entry's own tags merged on top); aws_codebuild_source_credential and aws_codebuild_webhook accept no tags argument at all in the current provider schema. tags_all is surfaced from the project only.
  • Eventual consistency: a newly created/updated service_role_arn may 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 an aws_codebuild_source_credential still referenced by build_source.auth, and before deleting the service role. CodeBuild does not hard-block on these, but a dangling reference produces confusing BatchGetProjects results on the next plan.
  • No us-east-1 global-resource constraint — CodeBuild is fully regional.
  • source is a Terraform-reserved variable name — this module's primary-source variable is named build_source; it renders the provider's source {} block unchanged.
  • export_config.s3_destination is unconditionally required on report_groups, per the live v6.53.0 provider schema — even NO_EXPORT report groups must supply a (functionally unused) bucket/encryption_key_arn pair.

🧱 Design Principles

Secure-by-default posture and the explicit opt-out for each:

Hardened defaultBehaviorOpt-out / control
Build artifact encryptionCustomer-managed KMS key when encryption_key_arn is supplied; AWS-managed S3 key (aws/s3) otherwise — never unencryptedartifacts.encryption_disabled = true (S3 artifacts only)
Docker-in-Docker / privileged buildsenvironment.privileged_mode = falseenvironment.privileged_mode = true (image-building workloads only — elevated blast radius, host Docker socket access)
Network placementNo vpc_config — builds run in CodeBuild's own isolated, no-inbound-exposure networkSupply vpc_config (all three fields together) for private-VPC connectivity
Report-group export encryptionencryption_disabled = false; a KMS key is a required input, never optional-offNone — the underlying API does not currently allow disabling report encryption
Source-credential tokenssensitive = true on the variable; for_each driven by nonsensitive(keys(...)) so token values never need to be unmarkedNot an opt-out — tokens are never safe to display in plan/state output
CloudWatch LogsON by default even with logs_config = null (CodeBuild's own default)logs_config.cloudwatch_logs.status = "DISABLED"
Image pull credentialsimage_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.


🚀 Runbook

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 / apply require valid AWS credentials and a configured region. Always pin the module source with ?ref=v1.0.0, never a branch.


🧪 Testing

  • terraform init -backend=false && terraform validate — schema + reference integrity.
  • terraform fmt -check — formatting.
  • terraform plan against a sandbox account — confirm iam:PassRole succeeds 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 before apply — otherwise codebuild:CreateWebhook fails with ResourceNotFoundException.

💬 Example Output

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" }

🔍 Troubleshooting

  • AccessDeniedException on CreateProject/UpdateProject even though codebuild:CreateProject is granted: Missing iam:PassRole on service_role_arn (or build_batch_config.service_role_arn) for the Terraform identity. This is the single most common first-apply failure for this module — check iam:PassRole before anything else.
  • Tag drift / unexpected tags:default_tags overlap. tags_all (project only) merges resource tags over provider default_tags, resource tags winning — set the value explicitly in var.tags if it looks wrong. Remember aws_codebuild_source_credential/aws_codebuild_webhook carry no tags at all.
  • Credential-chain failures (NoCredentialProviders / ExpiredToken): No valid AWS credentials resolved. Set AWS_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_config is set but service_role_arn lacks ENI permissions (ec2:CreateNetworkInterface and related actions) — this module cannot grant them; add them to the service role's policy.
  • ResourceNotFoundException: Could not find access token for server type github on 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 build step, not at plan/apply:environment.privileged_mode is false (secure default). Set it true only for workloads that genuinely build/push images.
  • Rotated a source-credential token but Terraform shows no diff: Expected — AWS never returns token on read, so Terraform cannot detect out-of-band rotation. Change the variable value and re-apply.
  • aws_codebuild_webhook.secret is null or 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_credentials entry for the same server_type silently replaces the first: CodeBuild allows only one credential per server_type per account per Region — this is an AWS-side limit this module cannot enforce across separate terraform apply runs/states.

🔗 Related Docs

  • Terraform Registry — hashicorp/aws provider: 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."