DEV Community

Cover image for Terraform in CI: plan on PR, apply on merge
Desly Peter
Desly Peter

Posted on Originally published at deslypeter.com

Terraform in CI: plan on PR, apply on merge

Stop running Terraform from laptops. How I automated it.

In many teams, Terraform is still run from someone's laptop. They pull the latest code, run plan, look at it, run apply. It works, until it doesn't: someone applies an old branch, two people apply at once, the laptop has different provider versions, or the only person with production credentials is on holiday.

The fix is a simple, well-known workflow: plan on pull request, apply on merge. Here's how I set it up.

The workflow

  1. A developer changes infrastructure code on a branch and opens a pull request.
  2. CI runs terraform plan and posts the plan as a comment on the pull request.
  3. Reviewers look at both the code and the plan.
  4. After approval, the pull request is merged.
  5. CI runs terraform apply with the plan, from the main branch.

Everything is reviewed, everything is logged, nobody needs production credentials on their laptop, and the main branch always reflects what's deployed.

The plan is the real change. Review the plan as carefully as the code.

Option 1: Atlantis

Atlantis is an open-source server built exactly for this workflow. It listens to pull request events, and you control it with comments:

  • Open a PR, and Atlantis automatically runs plan and comments the output.
  • Comment atlantis apply and it applies, then comments the result.
  • It locks the affected directories so two PRs can't change the same state at the same time.

Atlantis runs in your own infrastructure (often on Kubernetes), so credentials never leave your environment. It's simple, battle-tested and free. You do have to host and maintain it.

Option 2: GitHub Actions (or any CI)

You can build the same workflow in your normal CI. A simplified GitHub Actions version:

name: terraform

on:
  pull_request:
    paths: ["infrastructure/**"]
  push:
    branches: [main]
    paths: ["infrastructure/**"]

permissions:
  contents: read
  id-token: write
  pull-requests: write

jobs:
  terraform:
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: infrastructure/live/production/network
    steps:
      - uses: actions/checkout@<sha> # v4
      - uses: hashicorp/setup-terraform@<sha> # v3
      - uses: aws-actions/configure-aws-credentials@<sha> # v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/terraform-${{ github.event_name == 'push' && 'apply' || 'plan' }}
          aws-region: eu-central-1
      - run: terraform init -input=false
      - run: terraform plan -input=false -out=tfplan
      - if: github.event_name == 'pull_request'
        run: terraform show -no-color tfplan > plan.txt
        # post plan.txt as a PR comment with your preferred action
      - if: github.event_name == 'push'
        run: terraform apply -input=false tfplan
Enter fullscreen mode Exit fullscreen mode

Things to notice:

  • Two different IAM roles: a read-only role for plans on pull requests, and a role with write access only for applies from main. The trust policy restricts which branch can assume which role.
  • OIDC, no stored cloud keys.
  • Use GitHub environments with required reviewers for production applies if you want an extra approval step.

One subtlety: the plan on the pull request and the apply after merge can differ if main changed in between. Re-planning on merge (as above) and applying that plan is the safe way.

Option 3: TACOS

"TACOS" (Terraform Automation and Collaboration Software) is the nickname for platforms built for this: HCP Terraform, Spacelift, env0, Scalr, Harness IaCM and others. They add:

  • Policy as code (OPA or Sentinel) on every plan.
  • Drift detection and scheduled runs.
  • Stack dependencies, cost estimation, approvals, audit logs.
  • Self-service and RBAC for many teams.

They cost money, but save a lot of building and maintaining for larger organisations.

Approvals

Who can approve infrastructure changes? Some practical rules:

  • CODEOWNERS so the right team reviews changes to their part of the infrastructure.
  • Required reviews in branch protection.
  • Extra approval for production applies, through CI environments or the platform.
  • Plans visible to reviewers, ideally summarised: number of resources added, changed and destroyed. A "destroy" in production should stand out.

Secrets and OIDC authentication

Never store long-lived cloud keys in CI for Terraform. Use OIDC with every major cloud, and scope the roles tightly. Combine that with remote, encrypted, locked state, and the most sensitive parts of your setup never touch a developer's laptop.

So, how do I start?

Pick one Terraform stack, ideally a low-risk one. Set up plan-on-PR with a read-only role first, so the team gets used to reviewing plans. Then add apply-on-merge with a separate role. Once it works, roll it out to the other stacks and take away production write access from laptops.

My take: infrastructure changes deserve the same pipeline discipline as application code. Review the plan, merge, and let the robot apply.

— Desly


Originally published on deslypeter.com.

Top comments (0)