Terraform CI Drift: Preventing Pipeline Divergence
CI drift occurs when pipeline behavior diverges from local behavior or from reviewed intent. This leads to unreviewed applies, inconsistent infrastructure state, and broken trust in the delivery process.
Symptoms of CI Drift
- CI plan differs from local plan unexpectedly
- Apply occurs without using the reviewed plan artifact
- Provider/runtime drift appears between runs
- Scanner/policy stages are skipped on some paths
Root Causes
- Unpinned runtime/provider versions — different versions produce different plans
- Missing or stale lockfile — providers resolve differently across environments
- Apply re-runs plan — apply job runs
planagain instead of consuming the reviewed artifact - Inconsistent auth — different credentials between plan and apply stages
Drift Prevention Baseline
- Pin runtime and provider version ranges
- Commit lockfile and review lockfile changes
- Generate one reviewed plan artifact and apply exactly that artifact
- Run policy/security checks on every path to apply
- Enforce branch protections and environment approvals
Production-Ready GitHub Actions Template
name: terraform-delivery
on:
pull_request:
paths:
- '**/*.tf'
- '**/*.tfvars'
push:
branches: [main]
paths:
- '**/*.tf'
- '**/*.tfvars'
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: read
id-token: write
pull-requests: write
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform fmt -check
- run: terraform init -backend=false
- run: terraform validate
- run: terraform init
- run: terraform plan -out=plan.bin
- run: terraform show -json plan.bin > plan.json
- run: conftest test plan.json --policy policy/
- uses: actions/upload-artifact@v4
with:
name: reviewed-plan
path: plan.bin
apply:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [validate]
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- uses: actions/download-artifact@v4
with:
name: reviewed-plan
- run: terraform init
- run: terraform apply -auto-approve plan.bin
Notes:
- Replace auth steps with OIDC/provider-specific login actions
- In real repos, split plan/apply workflows if artifact lifetime across events is an issue
Production-Ready GitLab CI Template
stages:
- validate
- plan
- policy
- apply
variables:
TF_IN_AUTOMATION: "true"
validate:
stage: validate
image: hashicorp/terraform:1.7
script:
- terraform fmt -check
- terraform init -backend=false
- terraform validate
plan:
stage: plan
image: hashicorp/terraform:1.7
script:
- terraform init
- terraform plan -out=plan.bin
- terraform show -json plan.bin > plan.json
artifacts:
paths:
- plan.bin
- plan.json
expire_in: 24h
policy:
stage: policy
image: openpolicyagent/conftest:latest
script:
- conftest test plan.json --policy policy/
dependencies:
- plan
apply:
stage: apply
image: hashicorp/terraform:1.7
when: manual
allow_failure: false
script:
- terraform init
- terraform apply -auto-approve plan.bin
dependencies:
- plan
LLM Mistake Checklist
Common model mistakes the Terraform skill corrects:
- Missing lockfile strategy
- Apply without saved plan artifact
- No policy stage despite claiming compliance
- No branch/environment protection discussion
Quick Diagnostics
- Compare runtime versions local vs CI
- Diff lockfile in PR
- Ensure apply consumes
plan.binfrom the reviewed plan stage - Verify policy scanner runs on every apply path