A safe Terraform pipeline in GitLab separates configuration checks, planning, review, and infrastructure changes. The plan job creates a saved plan; an authorized person reviews it; and a gated apply job consumes that same plan. Use persistent remote state, backend locking where available, tightly scoped credentials, and restricted artifacts so the pipeline does not turn a review step into an uncontrolled infrastructure mutation.
How the Terraform pipeline works
Terraform’s core workflow is init, plan, and apply. Initialization configures the working directory, backend, providers, and modules. Planning compares the configuration with state and infrastructure and previews proposed changes without applying them. Applying a saved plan carries out the actions in that plan. HashiCorp’s Terraform documentation describes this sequence in Terraform CLI and Running Terraform in automation.
- Check the proposed configuration. Run formatting and validation checks so basic errors are caught before an infrastructure plan is produced.
- Initialize and plan. Configure the intended backend and run
terraform plan -out=tfplanto save the proposed actions. - Review the result. Make the plan available to the people responsible for the change, and obtain the approval required by your production policy.
- Apply the reviewed plan. The apply job should use the saved plan from the plan job, not generate a fresh plan after approval.
GitLab defines pipelines in a project’s .gitlab-ci.yml. Jobs run on runners, stages run in sequence, and jobs in the same stage can run in parallel. Pipelines can be triggered by events such as branch pushes and merge requests, or started manually. GitLab documents these pipeline concepts in CI/CD pipelines and Pipeline architectures.
A cautious GitLab CI example
This skeleton illustrates the job boundaries and saved-plan handoff; it is not a drop-in configuration for every repository. It assumes Terraform is installed on the selected runner, the repository contains the configuration, and the backend is configured for the environment. Adapt the runner, backend, branch rules, artifact controls, and credential integration to your GitLab and Terraform versions. Test the rules and artifact handoff before relying on the pipeline for production.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
stages:
- validate
- plan
- apply
validate:
stage: validate
script:
- terraform fmt -check -recursive
- terraform init -backend=false -input=false
- terraform validate
plan:
stage: plan
script:
- terraform init -input=false
- terraform plan -input=false -out=tfplan
artifacts:
paths:
- tfplan
- .terraform/
- .terraform.lock.hcl
apply:
stage: apply
needs:
- job: plan
artifacts: true
script:
- terraform apply -input=false tfplan
when: manual
allow_failure: false
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual
The example makes the apply job manual and restricts it to the default branch, but a manual job alone does not define who is authorized to approve a production change. Configure GitLab permissions and any environment approval controls to match your policy. A merge-request plan is useful feedback, but the plan to approve and apply should be generated against the current shared branch and state: merge ordering or infrastructure changes can make an earlier proposal stale.
Why the plan job exports more than the plan file
When plan and apply run in different jobs, they may run on different machines. The later job needs the saved plan and the initialized working-directory artifacts it relies on. The example transfers .terraform/ as well as tfplan; passing only the plan file is not guaranteed to provide everything the apply job needs. Confirm the required files and backend behavior for your Terraform version and runner setup.
Keep dependencies consistent
Commit .terraform.lock.hcl so provider selections are recorded and future initialization uses those selections by default. Pin the Terraform version in the runner environment rather than silently inheriting whatever version happens to be installed. If you use a GitLab CI/CD component or another included pipeline definition, pin it to a specific version where possible and review how included configuration merges with your jobs; same-named jobs or settings can interact unexpectedly.
Rank #2
Choose and protect the state backend
Terraform state maps resource addresses in configuration to real infrastructure objects. CI needs persistent state that later runs can retrieve and update; local state on an ephemeral runner is not an adequate team workflow. Choose a backend based on persistence, locking, access controls, backup and recovery, operational ownership, and fit with your deployment architecture.
Use a backend that supports locking when concurrent operations are possible. HashiCorp explains in Running Terraform in automation that locking protects against race conditions from concurrent runs; not every backend supports it. Avoid disabling locking as a workaround for competing runs, because two writers can interfere with state.
GitLab Self-Managed state is one option
GitLab documents Terraform state storage for GitLab Self-Managed installations. Its administration documentation says state files are encrypted before storage and that the feature is enabled by default. The documented default is local storage for relevant installations, with supported object-storage configurations also available; Helm chart installations use external object-storage configuration. These details apply to GitLab Self-Managed, not every GitLab deployment, and should be checked against the current administration documentation for the installation.
Rank #3
For Self-Managed administrators, verify the storage arrangement and backup plan before migrating state: GitLab warns that migration from object storage back to local storage is not possible. Recovery also depends on access to encrypted state files and the database; the documented decryption process requires the application secret and project ID. Treat state recovery as an operational responsibility, not merely a pipeline setting.
Control who can change infrastructure
A plan is a preview; an apply changes infrastructure. For production, HashiCorp recommends human review and warns about automatic approval when unintended destructive actions could cause downtime. The important control is not simply that an approval occurred, but that the apply job consumes the exact saved plan that was reviewed. If the plan is regenerated after approval, the reviewer may be authorizing a different set of actions.
A practical design is to use merge-request pipelines for early feedback, then create the production plan from the merged branch and current state. Make production apply a gated job and limit who can run or approve it through the project’s permissions and environment policy. The precise GitLab controls available depend on the project configuration and product setup.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Handle credentials and artifacts as sensitive data
Terraform credentials should be available only to the jobs and environments that need them. GitLab describes CI/CD variables as less secure than secrets-management providers: they can be overridden, may be accessible to people with settings access if not hidden, or may be exposed through a misconfigured pipeline. Prefer a secrets manager for highly sensitive secrets. If sensitive values must be CI/CD variables, GitLab advises masking, hiding, and protecting them where possible; scope them narrowly and avoid printing them in scripts or logs.
Saved plan files and initialized directories are operational artifacts and may contain sensitive values or configuration details. Restrict artifact access to the jobs and people who need it, and set retention in line with your policy. Do not assume a plan report is safe to publish merely because it is formatted for a merge request.
Plan visibility and OpenTofu reports
GitLab documents a terraform report path for an OpenTofu tfplan.json file that can display information in a merge-request widget. Its documentation requires JQ processing to remove credentials before the report is uploaded. This is a documented OpenTofu JSON report workflow, not a general instruction to upload any Terraform binary plan file as that report. Protect the report input and inspect what it exposes before enabling the integration.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsScale the pipeline without losing control
For a small repository, separate validation, plan, and apply jobs or stages are usually easier to inspect. GitLab stages provide a straightforward sequence; needs can express more direct job dependencies and artifact handoffs. For larger setups, GitLab supports parent-child pipelines to split work within a project and multi-project pipelines to coordinate across projects. Whichever structure you choose, keep the production plan-to-apply relationship explicit and ensure that the correct state and credentials reach only the intended jobs.
- Backend: verify persistence, locking support, access control, backups, recovery, and who operates it.
- Approval: decide who reviews production plans and who can start the apply job.
- Artifacts: transfer the saved plan and necessary initialized files, while limiting access and retention.
- Credentials: prefer a secrets manager for highly sensitive values; otherwise use protected, hidden, masked, and narrowly scoped variables where available.
- Dependencies: commit the provider lock file and pin Terraform and reusable pipeline components deliberately.
GitLab and HashiCorp product details, report support, configuration labels, and Terraform behavior can change. Check the current documentation for your versions and validate the pipeline in a non-production environment before using it to change production infrastructure.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

