Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 9 min read

GitLab CI + Terraform: A Safe, Reviewable Infrastructure Pipeline

Run Terraform from GitLab CI with the managed state backend, plan-on-MR, gated apply, and locking — so infra changes get reviewed like code instead of YOLO'd from a laptop.

  • #gitlab
  • #cicd
  • #terraform
  • #iac
  • #infrastructure
  • #gitops

The worst infrastructure outage I’ve cleaned up came from a Terraform apply someone ran from their laptop with stale state. No review, no plan in front of another human, no lock — just terraform apply -auto-approve and a bad afternoon for everyone. The fix wasn’t a better engineer. It was moving Terraform into GitLab CI, where every infra change becomes a reviewable merge request with a plan attached and an apply that a human has to click.

This is how to build that pipeline.

The shape of a good Terraform pipeline

The pattern that has never let me down has four properties:

  1. State lives in a managed backend with locking — never on a laptop, never in the repo.
  2. terraform plan runs automatically on every merge request and the plan is visible to reviewers.
  3. terraform apply is manual and gated — it never runs without a human clicking it, and only from the default branch.
  4. The pipeline holds the credentials, not individuals — via OIDC, ideally.

Get those four right and infra changes get the same review discipline as application code.

Use GitLab’s managed Terraform state

GitLab ships an HTTP state backend with locking built in, so you don’t need to stand up a separate S3 bucket and DynamoDB lock table. Point Terraform at it:

terraform {
  backend "http" {}
}

Then configure it in CI using GitLab’s predefined variables:

variables:
  TF_STATE_NAME: production
  TF_ADDRESS: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}"

.terraform_init: &terraform_init
  - terraform init
      -backend-config="address=${TF_ADDRESS}"
      -backend-config="lock_address=${TF_ADDRESS}/lock"
      -backend-config="unlock_address=${TF_ADDRESS}/lock"
      -backend-config="username=gitlab-ci-token"
      -backend-config="password=${CI_JOB_TOKEN}"
      -backend-config="lock_method=POST"
      -backend-config="unlock_method=DELETE"
      -backend-config="retry_wait_min=5"

The lock matters more than anything. With it, two pipelines can’t apply against the same state simultaneously — the second one waits or fails fast instead of corrupting state. That single property eliminates a whole class of outages.

Plan on every merge request

The plan is the review artifact. A reviewer looking at an infra MR should see exactly what will change — resources created, modified, and (the scary one) destroyed — without running anything themselves:

stages: [validate, plan, apply]

plan:
  stage: plan
  image: hashicorp/terraform:1.9
  script:
    - *terraform_init
    - terraform plan -out=plan.cache
    - terraform show -json plan.cache > plan.json
  artifacts:
    paths: [plan.cache]
    reports:
      terraform: plan.json
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

The reports: terraform: plan.json is the magic line — GitLab renders a summary widget right in the merge request: “3 to add, 1 to change, 2 to destroy.” Reviewers see the blast radius before approving. The -out=plan.cache saved as an artifact means the apply runs exactly the plan that was reviewed, not a freshly-computed one that might differ.

Gate the apply behind a human

Apply is where things get real. It must be manual, and it must only run on the default branch:

apply:
  stage: apply
  image: hashicorp/terraform:1.9
  script:
    - *terraform_init
    - terraform apply plan.cache
  dependencies: [plan]
  rules:
    - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
      when: manual
  environment:
    name: production

when: manual means someone has to click “play” in the pipeline UI. Applying plan.cache — the exact artifact from the reviewed plan job — guarantees no drift between review and apply. The environment: production ties it into GitLab’s deployment tracking and lets you add protected-environment approvals on top.

Credentials via OIDC, not stored keys

Don’t store long-lived cloud credentials as CI variables. Use OIDC so GitLab exchanges a short-lived job token for temporary cloud credentials scoped to the job. We cover the full OIDC setup in our GitLab CI/CD guides, but the principle is the same as everywhere else in this stack: the pipeline gets a credential that lives for minutes, not a key that lives until someone rotates it (which is to say, forever).

Where AI helps — and where it absolutely must not

A terraform plan is exactly the kind of dense output that AI summarizes well. Paste the plan and ask “what’s the actual impact here, and is anything being destroyed that shouldn’t be?” The model is good at catching the buried forces replacement on a database that’s about to be recreated rather than updated in place — the line a tired reviewer scrolls past.

But draw the same hard line we draw for incidents: AI reads and reasons; humans approve and apply. The model can explain the plan and flag the scary diff. It does not click apply. The whole point of this pipeline is that a human looks at a real plan before infrastructure changes — don’t automate that human away. Our AI Code Review assistant is built around exactly this: surface the dangerous line in the diff, explain why it’s dangerous, and leave the decision to you.

The payoff

Once Terraform lives in this pipeline, infra changes stop being a thing that happens to you and start being a thing you review like any other change. Plan on MR, gated apply, managed state with locking, OIDC creds. No more laptop applies, no more stale state, no more bad afternoons.

Terraform plans and AI-generated plan summaries are assistive, not authoritative. Always read the real plan and confirm the blast radius before applying to production infrastructure.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.