AWS CodeBuild provides a fully managed build service that spins up a fresh container for each build and tears it down when the build completes. This model removes the need to manage build servers and makes it well suited for Terraform automation where reproducible environments and clean isolation matter. When combined with AWS CodePipeline, CodeBuild can drive a plan-then-apply workflow for Terraform code stored in a GitHub repository, with manual approval gating between the two stages.
CodeBuild Projects for Terraform Plan and Apply
A common pattern for safe Terraform automation is to create two distinct CodeBuild projects, one responsible for generating a plan artifact and a second responsible for applying that plan. The separation allows the plan output to be inspected, approved, and then reused without re-running terraform plan.
The Plan project is configured with a service role, a 15 minute build timeout, and a CODEPIPELINE artifact type so the plan file can be passed downstream.
resource "aws_codebuild_project" "terraform_plan" {
name = "terraform-plan"
description = "Run terraform plan"
service_role = aws_iam_role.codebuild_role.arn
build_timeout = 15
artifacts {
type = "CODEPIPELINE"
}
environment {
type = "LINUX_CONTAINER"
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
privileged_mode = false
}
logs_config {
cloudwatch_logs {
group_name = "terraform-plan-logs"
}
}
source {
type = "CODEPIPELINE"
buildspec = <<EOF
...
EOF
}
}
The Apply project mirrors the Plan configuration but uses a different log group and a buildspec that consumes the saved plan.
resource "aws_codebuild_project" "terraform_apply" {
name = "terraform-apply"
description = "Run terraform apply"
service_role = aws_iam_role.codebuild_role.arn
build_timeout = 15
artifacts {
type = "CODEPIPELINE"
}
environment {
type = "LINUX_CONTAINER"
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
privileged_mode = false
}
logs_config {
cloudwatch_logs {
group_name = "terraform-apply-logs"
}
}
source {
type = "CODEPIPELINE"
buildspec = <<EOF
...
EOF
}
}
Both projects use LINUX_CONTAINER with BUILD_GENERAL1_SMALL compute and the image aws/codebuild/amazonlinux2-x86_64-standard:3.0 with privileged_mode set to false.
| Attribute | terraform-plan | terraform-apply |
|---|---|---|
| name | terraform-plan | terraform-apply |
| description | Run terraform plan | Run terraform apply |
| build_timeout | 15 | 15 |
| artifacts.type | CODEPIPELINE | CODEPIPELINE |
| environment.type | LINUX_CONTAINER | LINUX_CONTAINER |
| environment.compute_type | BUILDGENERAL1SMALL | BUILDGENERAL1SMALL |
| environment.image | aws/codebuild/amazonlinux2-x86_64-standard:3.0 | aws/codebuild/amazonlinux2-x86_64-standard:3.0 |
| logsconfig.cloudwatchlogs.group_name | terraform-plan-logs | terraform-apply-logs |
| source.type | CODEPIPELINE | CODEPIPELINE |
BuildSpec Configuration and Terraform Installation
The buildspec for both projects is version 0.2 and performs an explicit Terraform install in the install phase because the standard image does not include Terraform.
version: 0.2
phases:
install:
runtime-versions:
python: 3.8
commands:
- wget https://releases.hashicorp.com/terraform/1.0.11/terraform_1.0.11_linux_amd64.zip
- unzip terraform_1.0.11_linux_amd64.zip
- mv terraform /usr/local/bin/
pre_build:
commands:
- echo "Starting Terraform plan phase..."
- terraform --version
build:
commands:
- terraform init -input=false
- terraform plan -input=false -out=tfplan
post_build:
commands:
- echo "Completed Terraform plan phase"
artifacts:
files:
- tfplan
- .terraform/**/*
- '**/*'
The Apply buildspec follows the same install pattern and then runs apply against the saved plan.
version: 0.2
phases:
install:
runtime-versions:
python: 3.8
commands:
- wget https://releases.hashicorp.com/terraform/1.0.11/terraform_1.0.11_linux_amd64.zip
- unzip terraform_1.0.11_linux_amd64.zip
- mv terraform /usr/local/bin/
pre_build:
commands:
- echo "Starting Terraform apply phase..."
- terraform --version
build:
commands:
- terraform init -input=false
- terraform apply -input=false tfplan
post_build:
commands:
- echo "Completed Terraform apply phase"
artifacts:
files:
- '**/*'
The Plan buildspec exports tfplan, the .terraform directory and all files as CODEPIPELINE artifacts. The Apply buildspec exports all files to preserve outputs.
Artifact Passing and Pipeline Integration
Using CODEPIPELINE artifacts ensures the plan file generated by the Plan project is available to the Apply project without re-computation. The first project is the Plan stage and the second is the Apply stage in a CodePipeline definition.
The intended flow is:
- CodePipeline downloads source code from the API Gateway repository
- Run a Planning Step in AWS CodeBuild
- Download and Install Terraform
- Initialise the Terraform Environment with an S3 Backend
- Run the Terraform Plan and save the output to an Artifact
- Send an Email via SNS to say the pipeline is awaiting approval
- Await Manual Approval
- Run an Apply Step in AWS CodeBuild
- Download and Install Terraform
- Initialise the Terraform Environment with an S3 Backend
- Run the Terraform Apply using the Artifact from the Planning stage
This method of planning and applying terraform via CodeBuild can be useful when deploying terraform in an automated way whenever the repository containing the terraform is updated, while still keeping a manual approval step in place, and allowing the plan to be reviewed in the CodeBuild plan step logs.
Terraform Cloud Managed CodeBuild Module
A Terraform module exists to deploy Terraform-managed AWS CodeBuild. The module is authored by Tony Vattahil and is noted as being in alpha state and likely to contain bugs, with updates potentially introducing breaking changes. It is not recommended for production use at this time.
The module workflow involves:
- Install Terraform
- Sign up and log into Terraform Cloud
- Generate a Terraform Cloud token
- Run
terraform login - Export the
TERRAFORM_CONFIGvariable - Export credentials via
TERRAFORM_CONFIG="$HOME/.terraform.d/credentials.tfrc.json" - Clone the repository
- Change to the module root directory
- Set up the Terraform Cloud workspace
- Run
terraform initandterraform apply - Change to the deploy directory and edit
dev.auto.tfvars
Example .tfvars content uses AWS credentials:
AWS_SECRET_ACCESS_KEY = "<AKIAIOSFODNN7EXAMPLE>"
AWS_ACCESS_KEY_ID = "<wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY>"
AWS_SESSION_TOKEN = "AQoDYXdzEJr...<remainder of security token>"
A warning is provided to secure credentials outside of version control and follow secrets-management best practices. STS-based credentials are optional but highly recommended.
Basic CodeBuild Project Pattern
Every CodeBuild project needs an IAM service role, a source configuration, an environment definition, and an artifacts configuration.
A basic example for a general app build is:
resource "aws_codebuild_project" "main" {
name = "my-app-build"
description = "Build and test my-app"
build_timeout = 15
service_role = aws_iam_role.codebuild.arn
artifacts {
type = "NO_ARTIFACTS"
}
environment {
compute_type = "BUILD_GENERAL1_MEDIUM"
image = "aws/codebuild/amazonlinux-x86_64-standard:5.0"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
environment_variable {
name = "APP_ENV"
value = "production"
}
environment_variable {
name = "DB_PASSWORD"
value = "production/database/password"
type = "SECRETS_MANAGER"
}
}
source {
type = "GITHUB"
location = "https://github.com/my-org/my-app.git"
git_clone_depth = 1
buildspec = "buildspec.yml"
}
logs_config {
cloudwatch_logs {
group_name = "/codebuild/my-app"
stream_name = "build"
}
}
tags = {
Environment = "production"
Team =
}
}
The nice thing about CodeBuild is you don't manage any build servers - it spins up a fresh container for each build and tears it down when it's done.
Pipeline Flow and Manual Approval
To improve automation and avoid blind deployment, the pipeline splits into stages:
- CodePipeline
- Download Source Code from API Gateway Repository
- Run a Planning Step in AWS CodeBuild
- Download and Install Terraform
- Initialise the Terraform Environment with an S3 Backend
- Run the Terraform Plan and save the output to an Artifact
- Send an Email via SNS to say the pipeline is awaiting approval
- Await Manual Approval
- Run an Apply Step in AWS CodeBuild
- Download and Install Terraform
- Initialise the Terraform Environment with an S3 Backend
- Run the Terraform Apply using the Artifact from the Planning stage
The main Terraform setup requires a backend and provider configuration. The required provider is:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
backend "s3" {
bucket =
}
}
Variables and Prerequisites
The pipeline variables used for configuration include:
variable "aws_region" {
description = "The AWS region to deploy resources into"
type = string
default = "eu-west-1"
}
variable "github_repo_owner" {
description = "GitHub repository owner"
type = string
default = "REPO_OWNER_NAME"
}
variable "github_repo_name" {
description = "GitHub repository name"
type = string
default = "REPO_NAME"
}
variable "github_branch" {
description = "GitHub repository branch"
type = string
default = "main"
}
variable "github_token" {
description = "GitHub OAuth token"
type = string
sensitive = true
}
variable "notification_email" {
description = "Email address to receive approval notifications"
type = string
}
These variables drive the CodePipeline source action and notification settings.
Conclusion
Deploying Terraform through AWS CodeBuild with a dedicated Plan and Apply project provides repeatable, auditable infrastructure changes. Using CODEPIPELINE artifacts to pass the tfplan file ensures the exact plan reviewed is the plan applied. The explicit Terraform installation step, the use of amazonlinux2-x86_64-standard:3.0 with BUILD_GENERAL1_SMALL, and CloudWatch log groups for terraform-plan-logs and terraform-apply-logs give consistent observability.
The workflow gains additional safety from a manual approval stage and SNS notifications, while the S3 backend preserves state across builds. For organizations seeking a reusable pattern, the alpha Terraform module for Terraform-managed CodeBuild offers a starting point, though its alpha status requires caution for production use. Combining these patterns with proper variable management for region, GitHub source, and notification email yields a maintainable CI/CD path for Terraform code.