Terraform in CI/CD: Plan on Pull Request, Apply on Merge
Running terraform apply from laptops leads to "who changed this?" moments and state conflicts. A CI workflow makes every change visible, reviewed and repeatable.
The flow
- A pull request changes Terraform code.
- CI runs
fmt,validateandplan, and shows the plan to reviewers. - After review and merge, CI applies the change.
GitHub Actions example
name: Terraform
on:
pull_request:
paths: ['infra/**']
push:
branches: [main]
paths: ['infra/**']
permissions:
id-token: write # OIDC to the cloud
contents: read
pull-requests: write # comment the plan
jobs:
terraform:
runs-on: ubuntu-latest
defaults:
run:
working-directory: infra/envs/prod
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/terraform-prod
aws-region: ap-south-1
- run: terraform fmt -check -recursive
- run: terraform init -input=false
- run: terraform validate
- 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
- if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -input=false tfplan
Hardening
- Use OIDC, not stored cloud keys.
- Give the plan role read-only permissions; only the apply job (in a protected environment with required reviewers) gets write access.
- Apply the exact plan that was reviewed where possible, and fail if the state changed since.
- Add policy and security checks (Checkov, Trivy config scanning, OPA) to the PR job.
- Use
concurrencyso two applies to the same state never run at once.
Dedicated tools like Atlantis, HCP Terraform or Spacelift package this workflow with locking, plan comments and approvals built in.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.