Deploying Terraform with AWS CodeBuild

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_CONFIG variable
  • 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 init and terraform 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.

Sources

  1. https://dev.to/aws-builders/deploying-terraform-code-via-aws-codebuild-and-aws-codepipeline-2l0
  2. https://github.com/aws-ia/terraform-aws-codebuild
  3. https://oneuptime.com/blog/post/2026-02-12-create-codebuild-projects-terraform/view

Related Posts