Chapter 29Lesson 03~220 minutes

Infrastructure-as-Code Pipelines, Terraform/OpenTofu Workflows, Plan Reviews, State Safety, and Drift Controls: Configuration, Design Choices, and Tradeoffs

Choose deliberately between MR and trusted-branch planning, saved plans and re-planning, gated and automatic apply, and centralized versus isolated state while accounting for current GitLab OpenTofu components and state/report behavior.

DesignOpenTofu componentGitLab stateresource_groupTradeoffs

Learning objectives

  • Choose where planning belongs: every MR, trusted branches, or both.
  • Compare applying a reviewed saved plan with re-planning after merge.
  • Choose manual/approval gates versus automatic apply based on blast radius and reversibility.
  • Design state boundaries per environment/team while minimizing reader/writer privileges.
  • Adopt current GitLab OpenTofu components rather than removed Terraform CI/CD templates.

1. Design starts with the mutation domain

Before debating YAML, identify the thing that must not receive conflicting mutations: a state name/workspace, a cloud account/region, a Kubernetes cluster namespace, a DNS zone, or another provider-side control plane. That identity drives backend separation, resource_group keys, authorization and rollback scope.

State layer Evidence to capture Why it matters
Source/revision CI_PIPELINE_SOURCE, ref, CI_COMMIT_SHA, pipeline/job IDs A plan is meaningful only for the exact source/configuration that produced it.
Compiled CI configuration Merged YAML, rules decision, component/tag inputs, plan/apply job graph Proves what GitLab actually scheduled, not what the repository file looks like in isolation.
IaC tool/provider identity OpenTofu/Terraform version, .terraform.lock.hcl digest, provider versions/checksums Apply semantics can change when tool/provider selections change.
Backend/state Backend address/name, state serial/lineage where available, lock owner/status State is operational data and may contain secrets; it is not an ordinary build artifact.
Plan artifact Saved plan filename, SHA-256 digest, source SHA, lockfile digest, creation job ID The review target must be a concrete plan, not “whatever a later plan says.”
Authorization Review/approval identity, manual gate, protected branch/environment or local simulation record Authorization to mutate infrastructure is independent of a successful plan.
Mutation serialization State lock plus GitLab resource_group or equivalent coordinator Prevents two otherwise-valid jobs from racing against the same target/state.
External/provider state Created/changed resource identity and read-only post-apply verification A successful apply process is not the same as target health or intended real-world state.
Drift Plan exit code, drift plan, changed resource identities, policy result Drift is an observation that requires review; it is not an instruction to auto-fix blindly.
Governance/evidence Plan/review/apply linkage, exceptions, cleanup/destroy plan and verification Makes mutation and recovery independently auditable.

2. Plan on every MR or only trusted branches?

Approach Strength Risk/cost Good fit
Plan every MR Fast reviewer feedback and visible proposed changes. Untrusted code can execute providers/modules; credentials and backend reads must be carefully restricted. Synthetic/read-only plans, isolated credentials, controlled modules/providers.
Plan trusted branch only Narrower credential exposure and fewer expensive refreshes. Reviewers see less precise pre-merge infrastructure impact. High-risk providers where MR pipelines cannot safely receive read credentials.
Two-level plan MR uses limited/read-only or local validation; trusted branch computes authoritative apply plan. More pipeline complexity and two distinct plan meanings. Most production teams with strong trust separation.

An MR plan is useful feedback only if the MR job is allowed to execute safely. Do not expose production write credentials merely to make a plan widget appear.

3. Saved plan versus re-plan after merge

A saved plan gives strong identity: the apply job consumes the exact reviewed change set. But a plan can become stale when state changes. Re-planning after merge uses fresher state and the merged source, but it creates a different plan from the one reviewed in the MR.

Choice What review approves Freshness Required evidence
Apply saved plan Exact binary plan from a known source/state snapshot. Can become stale; apply should fail safely if assumptions changed. Plan digest, source SHA, provider lock, state identity/serial if available, review record.
Re-plan after merge Policy/intent plus merged code; later authoritative plan needs its own review/gate. Fresher state and exact merged commit. MR advisory plan + merged SHA + new plan digest + post-merge approval/policy.

Never describe these as equivalent. If policy says “two people reviewed the MR plan,” it does not automatically mean “two people approved a newly generated production plan.”

4. Automatic apply versus explicit gate

Automatic apply can be appropriate for low-blast-radius, reversible, continuously reconciled environments. Production changes with destructive actions, weak rollback, regulatory obligations or shared state usually deserve a manual/approval control. The gate is not there to compensate for poor automation; it exists to authorize a known mutation after machine evidence is complete.

Separation of concerns: CI rules decide whether the apply job exists; a manual/approval control decides whether an actor authorizes it; backend/provider permissions decide whether it can mutate; external verification decides whether the result is acceptable.

5. Centralized state versus per-environment state

State layout Benefits Costs/risks Typical control
Single large state Simple dependency references and fewer backends. Large blast radius, long locks, broad read access, coupled teams. Narrow writers; careful module boundaries; strong serialization.
Per-environment Production/staging drift and authorization isolated. Cross-environment outputs need explicit sharing. Distinct state names, resource groups and credentials.
Per-service/domain Smaller blast radius and team autonomy. More interfaces and remote-state/data contracts. Versioned outputs/APIs rather than arbitrary state scraping.

State should follow ownership and mutation boundaries, not merely repository folders.

6. GitLab-managed state: current role and permission model

GitLab-managed OpenTofu/Terraform state is available on all tiers. Current documentation separates reading state from lock/write operations. For CI, CI_JOB_TOKEN can authenticate supported backend operations, and GitLab’s OpenTofu tooling handles the HTTP backend flow. Use the narrowest project role/job context that supports the required operation.

State is encrypted at rest by GitLab, but that does not make state harmless to readers. Avoid copying it into artifacts or logs.

7. Do not bake job credentials into a saved plan

GitLab documents a failure mode where passing HTTP backend credentials through -backend-config=password=$CI_JOB_TOKEN can persist job-specific configuration into a saved plan. A later apply job has a different token and may fail to lock state. Prefer the supported environment-variable/component configuration so each job authenticates at runtime rather than embedding the previous job’s token.

Security rule: never solve this with a broad long-lived PAT as the default. Fix backend configuration and identity lifetime first.

8. Current reusable pipeline direction: OpenTofu CI/CD component

The removed Terraform CI/CD templates are not a safe 2026 starting point. Pin a released OpenTofu component. Current release 4.8.1 is compatible with a documented set of OpenTofu versions including 1.12.5.

include:
  - component: gitlab.com/components/opentofu/validate-plan-apply@4.8.1
    inputs:
      version: 4.8.1
      opentofu_version: 1.12.5
      root_dir: infra/
      state_name: production

stages: [validate, build, deploy]

For higher assurance, verify the release page, use the component’s signed image support/digest input where practical, and record the resolved component/image identity in the evidence packet.

9. MR plan reports improve review but need a security model

artifacts:reports:terraform can render create/update/delete counts in a merge request. The report artifact is Free-tier available. GitLab also warns that plan data may contain secrets. Treat report generation as a presentation layer: sanitize the report, restrict artifact access, keep the authoritative plan identity separate, and retain only as long as needed.

10. Artifact access and retention are part of IaC design

Current GitLab supports artifacts:access values including developer, and newer releases also support maintainer. Use the narrowest role compatible with your team and remember that job-token access/visibility has separate controls.

artifacts:
  access: maintainer
  expire_in: 12 hours
  paths:
    - plan.cache
    - plan.sha256
    - evidence/plan-metadata.txt

Whether maintainer is appropriate depends on your GitLab version; it was introduced in GitLab 18.4. If you must support older versions, use a compatible access setting plus project/CI visibility controls.

11. Resource-group process mode is an ordering policy, not a lock replacement

Use one resource-group key per state/mutation domain. The default mode is unordered. oldest_first can preserve commit progression; newest_first and newest_ready_first can discard stale ordering assumptions but require idempotent jobs. Change process mode only when you understand how plan freshness and state history interact.

For IaC applies, blindly choosing “newest first” is risky if an older pipeline contains a reviewed prerequisite migration. Ordering must reflect your release model.

12. Drift schedule: detect, classify, then reconcile

A scheduled pipeline can run a read-only plan with -detailed-exitcode. It should retain the exact source, state/backend identity and provider set used for detection. Code 2 creates an issue/alert/review signal. Automatic apply should be a separate, explicit policy for only those resources where the team accepts that behavior.

13. OpenTofu versus Terraform in this GitLab course

Both tools share much of the workflow model, but GitLab’s maintained integration direction is OpenTofu. Terraform remains usable with your own images/templates and backend configuration. Do not assume a GitLab OpenTofu component is also a promise about every Terraform version or license. When a team uses Terraform, record its exact CLI/provider versions and own the pipeline maintenance explicitly.

14. Worked scenario: three environments, one platform team

A team manages dev, staging and prod. Developers need fast MR feedback; only platform maintainers may mutate prod.

Decision Selected design Observable evidence
MR feedback Run fmt/validate/security and a read-only staging-like plan with synthetic/no-prod credentials. MR pipeline source/SHA, plan report, no prod write identity.
State Separate dev, staging, prod state names. Backend state name and role/permission evidence.
Production plan Compute authoritative plan on protected default branch after merge. Default-branch SHA, provider lock digest, plan digest.
Apply Manual/approval gate, prod-specific resource group, exact saved plan. Approver/manual actor, job ID, plan digest verification, resource-group queue.
Drift Scheduled read-only prod plan; code 2 opens review path. Scheduled pipeline ID, drift plan, owner/exception record.

15. Decision table

Question Prefer A when… Prefer B when…
MR plan vs trusted plan MR identity can safely read an isolated target. Provider/backend access should be limited to trusted refs.
Saved plan vs re-plan Exact reviewed bytes are the primary control and state is stable enough. Merged-source freshness matters and a second approval can review the new plan.
Auto apply vs manual gate Low blast radius, high reversibility, strong tests/reconciliation. Shared/high-risk state, destructive changes or compliance requires explicit authorization.
Central vs split state Resources are tightly coupled under one owner. Ownership, environment or blast-radius boundaries differ.
Built-in component vs custom pipeline Supported component fits your workflow and compatibility window. You need Terraform/custom tooling or controls not supported by the component.

Knowledge check

Why might an MR plan intentionally use less privilege than the production apply job?

When is re-planning after merge acceptable?

What does a resource-group key need to align with?

Why is “encrypted state at rest” not enough to grant broad Developer access?

What replaces the removed Terraform CI/CD templates in current GitLab guidance?

Next lesson

Next lesson

Lesson 4 deliberately breaks these boundaries so you can diagnose sensitive plans, stale provider/code identity, lock conflicts, exposed state and blind drift automation from preserved evidence.

Version and compatibility note

GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.

Official references and version notes

Documentation verification date: 2026-09-12. GitLab-managed OpenTofu/Terraform state and merge-request plan integration are available on Free, Premium, and Ultimate. GitLab’s legacy Terraform CI/CD templates were removed in GitLab 18.0; current guidance is the versioned OpenTofu CI/CD component. GitLab-managed OpenTofu/Terraform state support in the current CLI flow was introduced in GitLab 18.3 and requires glab 1.66+. Plan files are not encrypted by GitLab or OpenTofu and can contain credentials or sensitive values; restrict artifact access and never treat plan output as safe by default. The current released OpenTofu component used for optional examples is 4.8.1; that release supports OpenTofu 1.12.5 and publishes signed rootless images. The standalone local lab pins OpenTofu 1.12.6 (latest stable on the verification date) and hashicorp/local 2.9.0. The component version and supported OpenTofu versions are release-specific. Re-check the selected component release before updating a production pipeline.

Current assumptions used in this chapter: Version-sensitive YAML, Runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations must be verified against the exact GitLab, GitLab Runner, tool, and external-system versions used in production. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for reproducible work.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.