Terraform in CI/CD & automation

Plan on PR, apply on merge, safely.

Advanced14 min · lesson 14 of 15

Running Terraform from laptops does not scale and is not safe: no review of what will change, no audit trail, credentials scattered across machines. The mature pattern automates it — plan on every pull request so reviewers see the exact diff, and apply automatically on merge, from CI, using short-lived credentials. Git becomes the source of truth and the audit log.

The plan-on-PR, apply-on-merge flow
1open PR
fmt, validate, scan, plan
2review
plan + policy posted to the PR
3merge
approved change lands
4apply
CI applies the saved plan
The plan you reviewed is the plan that applies (-out=tfplan), so nothing changes between approval and execution.

Save the plan, apply exactly it

The safety hinge is separating plan and apply across the review boundary. CI runs terraform plan -out=tfplan on the PR and posts it; a human approves; on merge, CI runs terraform apply tfplan — applying precisely the reviewed plan, with no recomputation. Tools like Atlantis wrap this whole flow around pull requests (comment atlantis plan / atlantis apply); Terraform Cloud, GitLab, and GitHub Actions all implement the same shape.

.github/workflows/terraform.yml
jobs:
plan:
permissions: { id-token: write, contents: read } # OIDC, not static keys
steps:
- uses: aws-actions/configure-aws-credentials@v4
with: { role-to-assume: arn:aws:iam::...:role/tf-ci, aws-region: us-east-1 }
- run: terraform init
- run: terraform plan -out=tfplan
- run: terraform show -json tfplan | conftest test - # policy gate
# apply job runs on merge to main, using the saved tfplan artifact

Credentials: OIDC, not long-lived keys

The biggest CI security win is deleting static cloud keys. Modern CI (GitHub Actions, GitLab) can exchange a short-lived OIDC token for a cloud role at run time, so the pipeline assumes a scoped IAM role for minutes instead of storing a permanent access key that could leak. Combined with a narrow role (only what this pipeline provisions) and a protected apply job, the automation is more secure than any laptop.

The CI runner holds the keys to your infrastructure
A pipeline that can apply Terraform can create, modify, or destroy your whole estate — it is the most privileged thing in your delivery system. Protect it accordingly: least-privilege OIDC roles (not admin), require review before the apply job runs, restrict who can trigger it, isolate the runner, and never expose the plan/state artifacts publicly (they contain secrets). Treat the Terraform pipeline as tier-0 infrastructure, because it is.