Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.

Terraform aws module type resources


🧩 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
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)

✅ Provider / Versions

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.


🔑 Required IAM Permissions

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


🔌 Cross-Module Contract

Consumes

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)

Emits

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

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

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


🚀 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."