Terraform

How to Manage Terraform State with GitLab [Tutorial]

How to Manage Terraform State with GitLab

Every Terraform project needs a place to store state, and for teams already running GitLab, the simplest option is often sitting right in GitLab itself. GitLab’s built-in HTTP backend stores your state encrypted at rest, handles locking and versioning automatically, and authenticates against the same GitLab roles you already use, with no extra cloud account or DynamoDB table to manage.

In this article, we will look at what GitLab CI/CD is, what features it brings to the table, and reasons why you might want to use it, before taking a look at how to use GitLab to manage your Terraform state files with some example configuration files.

What we will cover:

  1. What is GitLab CI/CD?
  2. What is a Terraform state file?
  3. Why use GitLab-managed Terraform state? 
  4. How to use GitLab to manage Terraform state
  5. How GitLab Terraform state compares to other backends
  6. Managing Terraform state with Spacelift

What is GitLab CI/CD?

CI/CD stands for Continuous Integration/Continuous Delivery and is a continuous method of software development.

GitLab CI/CD provides a .gitlab-ci.yml file at the root of your project where you can define your CI/CD pipeline configuration (it can be named as anything you wish but this is the most common name). This file follows the YAML format, has its own syntax, and specifies the stages, jobs, and scripts needed to build, test, and deploy your application. The CI/CD pipeline is triggered automatically whenever changes are pushed to the repository.

GitLab Runners are used to execute the CI/CD jobs. These runners can be distributed across different machines. A container image you want to use when running the job can be specified in the .gitlab-ci.yml file, the runner then loads the image, clones your project, and runs the job either locally or in the container.

Jobs define what you want to do with your pipeline. For example, to run a plan stage in your Terraform or deploy to a production environment. Jobs are grouped into stages. Each stage contains at least one job. Typical stages using a Terraform workflow might be initplan, and deploy. Jobs within the pipeline can be executed in parallel, optimizing the build and test times.

GitLab CI/CD (and other CI/CD systems) allow you to store and manage artifacts generated during the build process, which can be reused in subsequent stages.

Variables used in your pipelines can be defined directly in the .gitlab-ci.yml file, set in your GitLab project settings, or dynamically generated. Using GitLab CI/CD environment variables or HashiCorp Vault for sensitive information like API tokens and credentials is recommended. Also, ensure that appropriate access controls are in place within your GitLab repository.

A simple example .gitlab-ci.yml file for use with Terraform could look like this:

stages:
  - plan
  - apply

variables:
  TF_CLI_ARGS: "-input=false"

plan:
  stage: plan
  script:
    - terraform init
    - terraform plan -out=tfplan

apply:
  stage: apply
  script:
    - terraform apply tfplan
  when: manual
  only:
    - main

What is a Terraform state file?

Terraform stores information about the infrastructure it manages in a state file. This file tracks the current state of your infrastructure, including the resources Terraform has provisioned and their current configurations. The state file is essential for Terraform to plan and apply changes to your infrastructure.

  • The state file is persistently stored on disk and typically has a .tfstate extension.
  • Terraform uses a state file lock to prevent concurrent operations that might modify the state. This ensures that only one Terraform operation can be applied at a time, preventing conflicts.
  • The state file may contain sensitive information such as passwords or private keys (which can be mitigated by using the sensitive variables parameter when defining your variable). It’s important to limit access to the state file and avoid exposing it to unauthorized users.
  • Terraform “backend configuration” refers to where the state file is stored. It can be set to use local storage, or remote storage such as AWS S3 or Azure Storage account. It can also be stored on hosted platforms such as a Terraform Enterprise instance, Spacelift or GitLab.

Read more about managing Terraform state.

Why use GitLab-managed Terraform state?

GitLab provides a Terraform HTTP backend to securely store your state files in a remote and shared store with minimal configuration.

Benefits of managing Terraform state with GitLab include:

  • Ability to version your Terraform state files since they are held in a repository. You can track changes over time, roll back to previous states, and collaborate with team members using Git’s branching and merging capabilities. Storing Terraform state in a version-controlled system aligns with best practices for infrastructure as code.
  • GitLab provides a centralized repository for storing and version-controlling your Terraform state.
  • Encrypt the state file both in transit and at rest.
  • You can control who has access to the state files and ensure that sensitive information is handled securely.
  • GitLab’s collaboration features enable multiple team members to work on the same Terraform codebase concurrently. With merge requests and code reviews, you can ensure that changes are reviewed and approved before being applied to the infrastructure.
  • Lock and unlock states.
  • Remotely execute terraform plan and terraform apply commands.

How to use GitLab to manage Terraform state

Before we jump into the example:

  • You must enable the Infrastructure menu for your project. Go to Settings > General, expand Visibility, project features, permissions, and under Infrastructure, turn on the toggle.
  • An administrator must set up Terraform state storage.
  • To lock, unlock, and write to the state by using terraform apply, you must have at least the GitLab Maintainer role.
  • To read the state by using terraform plan -lock=false, you must have at least the GitLab Developer role.

The cleanest setup keeps backend.tf empty and passes the connection details in at init time. That way you can use a personal access token for local runs and CI_JOB_TOKEN (a short-lived token GitLab injects into every CI job) inside the pipeline, without touching the code.

backend.tf

terraform {
  backend "http" {}
}

For local runs, set the TF_HTTP_* environment variables that the HTTP backend reads automatically, then run terraform init:

export PROJECT_ID="<your-gitlab-project-id>"
export STATE_NAME="<your-state-name>"
export TF_HTTP_ADDRESS="https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/${STATE_NAME}"
export TF_HTTP_LOCK_ADDRESS="${TF_HTTP_ADDRESS}/lock"
export TF_HTTP_UNLOCK_ADDRESS="${TF_HTTP_ADDRESS}/lock"
export TF_HTTP_LOCK_METHOD="POST"
export TF_HTTP_UNLOCK_METHOD="DELETE"
export TF_HTTP_USERNAME="<your-gitlab-username>"
export TF_HTTP_PASSWORD="<your-personal-access-token>"   # token needs the api scope

terraform init

Inside a GitLab CI job, none of those values need to be hardcoded. GitLab provides CI_API_V4_URL, CI_PROJECT_ID, and CI_JOB_TOKEN automatically, so the same pattern in .gitlab-ci.yml looks like this:

variables:
  STATE_NAME: "default"
  TF_HTTP_ADDRESS: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${STATE_NAME}"
  TF_HTTP_LOCK_ADDRESS: "${TF_HTTP_ADDRESS}/lock"
  TF_HTTP_UNLOCK_ADDRESS: "${TF_HTTP_ADDRESS}/lock"
  TF_HTTP_LOCK_METHOD: "POST"
  TF_HTTP_UNLOCK_METHOD: "DELETE"
  TF_HTTP_USERNAME: "gitlab-ci-token"
  TF_HTTP_PASSWORD: "${CI_JOB_TOKEN}"

plan:
  stage: plan
  script:
    - terraform init
    - terraform plan -out=tfplan

CI_JOB_TOKEN is scoped to the job, expires when the job finishes, and respects the project’s CI/CD job token permissions, so there’s nothing to rotate and nothing to leak in the file. The Terraform official docs cover the full set of options the HTTP backend supports.

To view and manage individual Terraform state versions or remove state files in GitLab:

  1. On the left sidebar, select Search or go to and find your project.
  2. Select Operate > Terraform states.
gitlab ci cd infrastructure terraform

Note that GitLab provides two helpers to ease your integration with the GitLab-managed Terraform State, which you can use if you want.

  • The gitlab-terraform script, which is a thin wrapper around the terraform command.
  • The terraform-images container images, which include the gitlab-terraform script and terraform itself.

Read more: How to Implement GitLab CI/CD Pipeline with Terraform

How GitLab Terraform state compares to other backends

GitLab’s HTTP backend is not the only option for Terraform state, and the right choice depends on where the rest of your tooling already lives. The table below compares GitLab-managed state with the four backends most teams evaluate alongside it: AWS S3, Azure Blob Storage, HCP Terraform, and Spacelift.

Capability GitLab-managed state AWS S3 Azure Blob (azurerm) HCP Terraform Spacelift
Encryption at rest On by default On by default (SSE-S3); SSE-KMS opt-in via kms_key_id On by default On by default On by default
State locking Native, via HTTP backend Native via use_lockfile (Terraform 1.10+); DynamoDB deprecated in 1.11+, still supported in OpenTofu Native via blob leases, always on Automatic per workspace Automatic, scoped to active runs
Versioning Built-in, every state write Opt-in via S3 bucket versioning Opt-in via blob versioning Built-in, with diffs between versions Built-in, with rollback
Authentication PAT, CI_JOB_TOKEN, deploy tokens AWS IAM, OIDC Storage key, Entra ID, OIDC, managed identity API tokens, OIDC, SSO One-time credentials injected per run
Native state UI Operate > Terraform states None None Full UI with versions and rollback State history tab with rollback
Cost Included in your GitLab tier S3 storage, plus DynamoDB if used Azure Storage costs Free tier with a managed-resource cap, then per-resource billing on paid tiers Included in your Spacelift plan

Managing Terraform with Spacelift

If you need remote state management and the guardrails to manage your infrastructure automation safely, Spacelift is the platform for you.

Spacelift can optionally manage the Terraform state for you, offering a backend synchronized with the rest of the platform to maximize convenience and security. You can also import your state during stack creation, which is useful for engineers migrating their existing configurations and states to Spacelift.

Behind the scenes, Spacelift uses Amazon S3 and stores all the data in Ireland. Having Spacelift manage the state for you is straightforward. It is enabled by default without you needing to do anything. You can read more about how it actually works here.

At the same time, state is protected against accidental or malicious access because Spacelift maps state access and changes to legitimate Spacelift runs, automatically blocking all unauthorized traffic.

Beyond state management, Spacelift gives you policy as code with OPA, self-service infrastructure through Blueprints and Templates, automated drift detection and remediation, and end-to-end workflows with shared outputs. For teams looking to accelerate further, Spacelift Intelligence adds an AI-powered layer for natural language provisioning and operational insight across your infrastructure.

If you are interested in learning more about Spacelift, create a free account today or book a demo with one of our engineers.

Key points

GitLab CI/CD is a powerful tool that automates the software development lifecycle, ensures code quality, and streamlines the process of delivering software changes to production. Using it to manage your Terraform state file brings a key component of your infrastructure-as-code deployments under the same roof as your code repositories and pipelines and the versioning, security, and management benefits of this along with it.

We also encourage you to explore how Spacelift makes it easy to work with Terraform.

Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.

Achieve Terraform at scale with Spacelift

Spacelift takes managing infrastructure at scale to a whole new level, offering a more open, more customizable, and more extensible product. It’s a better, more flexible CI/CD for Terraform, offering maximum security without sacrificing functionality.

Learn more

Frequently asked questions

  • What is the best practice for managing Terraform state?

    The best practice for Terraform state is to store it remotely in a secure, centralized backend with state locking and versioning enabled. This ensures consistency, prevents corruption from concurrent runs, and allows for collaboration across teams.

  • What are the key features of GitLab-managed Terraform state?

    GitLab-managed Terraform state provides integrated storage and management of Terraform state files directly within GitLab, removing the need for external backends like S3 or Azure Blob Storage.

  • How do I migrate the existing Terraform state to GitLab?

    Configure the http backend pointing to your GitLab project’s state API, run terraform init -migrate-state to copy existing state from the current backend, and authenticate with a personal access token with api scope (or ${CI_JOB_TOKEN} in CI).

  • Does GitLab support Terraform workspaces?

    No. The HTTP backend GitLab uses doesn’t support Terraform workspaces, but GitLab does support multiple named state files per project — separate environments by pointing each job at a different state name in the URL (e.g., …/terraform/state/dev vs …/terraform/state/prod).

  • How do I fix a locked Terraform state in GitLab?

    Unlock through the GitLab UI under Operate > Terraform states, run glab opentofu state unlock <name>, or call DELETE /projects/:id/terraform/state/:name/lock, then verify no active pipeline is still holding the lock before retrying.

Terraform State at Scale

Get the three-stage maturity model
and a quick-reference checklist
for your platform team.

terraform state at scale bottom overlay
Share your data and download the guide