GitLab-managed OpenTofu and Terraform state

GitLab can hold OpenTofu state in the same instance as your code and pipelines. This page covers what it encrypts, who can write it, and what to plan before moving state off a separate backend.

On this page

Keeping GitLab-managed OpenTofu state in your own instance

If your infrastructure code already lives in self-managed GitLab, the state file is the piece most teams end up hosting somewhere else, usually an object store plus a locking service that someone must also patch, back up and secure. GitLab-managed OpenTofu state removes that second system. According to the GitLab documentation, this state storage is available in all tiers, so the choice does not depend on whether you buy Free, Premium or Ultimate. The state is versioned and encrypted at rest with the Lockbox gem, using a key derived from db_key_base, the secret that also protects other encrypted data in your instance. Writing or locking state requires the Maintainer or Owner role on the project, which is the control most reviewers will ask about first.

Teams usually choose this option when the people who change infrastructure already work in GitLab merge requests and pipelines, and when they want state access to follow project permissions instead of a separate cloud IAM model. The trade-off is that state is now protected by your GitLab backup and secret management, so db_key_base must be included in your backup plan and stored safely. If you lose that secret, encrypted state cannot be read. Confirm with your GitLab administrators how gitlab-secrets.json is backed up and tested, and check the GitLab OpenTofu state documentation for the current storage details before you commit.

OpenTofu is the client tool that runs init, plan and apply against this backend. It is licensed under the Mozilla Public License 2.0 and describes itself as a drop-in replacement for Terraform that keeps existing workflows and configurations. That description covers the workflow, but compatibility with future Terraform releases is not guaranteed, because the OpenTofu project decides independently which features it adopts. Before you move a codebase, pin the OpenTofu version in your pipelines, test a plan against a copy of the state and review the migration guidance for configurations that read other state through terraform_remote_state.

Which state storage option fits which team

This table compares GitLab-managed state, a self-hosted object store with a separate backend, and a managed cloud backend across ownership, key control, locking, versioning and operating load. Read each row as a question to answer for your team, and use the last row to confirm the details that depend on your chosen key provider.

CriterionGitLab-managed stateSelf-hosted object store with separate backendManaged cloud backend
Who runs the storageYour GitLab instance team, as part of the same platform that runs repositories and pipelinesYour team runs the object store and the backend configuration as a separate serviceThe cloud provider runs the storage service, your team owns the account and configuration
Encryption at restState is encrypted with the Lockbox gem using a key derived from db_key_baseDepends on the storage product and the OpenTofu state encryption settings you configureDepends on the cloud service and its key options, such as KMS-backed keys where supported
Encryption key controlKey lives with the GitLab instance secrets, so losing or rotating db_key_base affects state accessYou choose the key provider, for example OpenBao or a KMS, and manage its lifecycleKeys are held by the cloud account, and you choose between provider-managed and customer-managed options
LockingWriting or locking state requires the Maintainer or Owner role in the projectLocking depends on the backend you pick and must be tested before adoptionLocking depends on the backend you pick and must be tested before adoption
VersioningState is versioned inside GitLabRequires object store versioning to be enabled and tested by your teamUsually available as a storage setting, confirm retention and restore steps with the provider
Operational loadLow extra load beyond the GitLab instance backup and upgrade planHighest, since storage, access policy, backups and patching sit with your teamModerate, since the provider runs the service but your team still manages access and configuration
Dependence on GitLab availabilityState access depends on the GitLab instance being up and reachable from runnersState access depends only on the object store and network pathState access depends only on the cloud service and network path
Confirm before choosingConfirm the key provider and backup approach for db_key_base with your GitLab administratorsConfirm which OpenTofu key providers your storage and runners supportConfirm which key providers the cloud backend supports and who holds each key

Setting up GitLab-managed state step by step

This sequence takes a project from a self-managed GitLab instance with a working runner to OpenTofu state stored, locked and versioned in GitLab. Each step ends with a check so you can confirm the state is writable before any real infrastructure changes run.

  1. Confirm GitLab version and runner availability

    Ask your GitLab administrator for the installed version, which the Admin area displays, and confirm it is on a release that still receives fixes under the maintenance policy. Confirm that at least one runner is online for the project, with the executor and tags your pipeline needs. Check the runner by running a small test job that prints the OpenTofu version it finds. The step is complete when that job finishes successfully on the intended runner.

  2. Create the project and name the state

    Create or select the project that will hold your OpenTofu code, and record its numeric project ID from the project overview. Choose a state name for each separate configuration, such as one name per environment or per stack, because the name becomes part of the state endpoint. Keep state names stable, since renaming one later means pointing the backend at a new state. The step is complete when you have written down the project ID and state name for each configuration.

  3. Configure the OpenTofu backend block

    Add a terraform block that uses the http backend, with the address set to the project's Terraform state endpoint under the API path, followed by the state name. Set the lock and unlock addresses to the same endpoint with the lock path, the lock method to POST and the unlock method to DELETE, as described in GitLab's documentation for OpenTofu state. Keep usernames and tokens out of the file and supply them through CI/CD variables. The step is complete when tofu init reads the backend block without a syntax error, even if authentication has not yet been configured.

  4. Create a token with Maintainer or Owner role

    Create a project access token, or confirm that the CI job token can reach the state endpoint on your version, with the Maintainer or Owner role. Writing and locking state require one of these roles, so a Developer or Reporter token will read but fail to lock or write. Store the token as a protected, masked CI/CD variable and set the username and password variables that the http backend reads. Confirm the token scope and expiry with your security team, because the token will authenticate every pipeline run. The step is complete when tofu init authenticates and the project shows an empty state list.

  5. Add the OpenTofu CI/CD component

    Add the OpenTofu component from the CI/CD component catalog to the project's .gitlab-ci.yml, and pin it to a specific version rather than a moving reference. Confirm the component path and available versions on your instance, since catalog entries and supported releases can differ between installations. Define the validate, plan and apply stages so that plan runs on every merge request and apply runs only from the protected default branch. The step is complete when a pipeline starts and the validate job runs without errors.

  6. Run init and plan in the pipeline

    Let the pipeline run init, which connects to the GitLab state endpoint, and then plan, which writes a plan file for review. Read the plan output and confirm that it lists the resources you expect, with no unexpected destroy or replace actions. Open the project's Terraform states list and check that the state name appears. The step is complete when the plan job succeeds and the state is visible in GitLab, although no state version has been written yet.

  7. Apply a checked-in change

    Commit a small, reviewable change to the OpenTofu code on a branch and open a merge request so the plan output is attached to the review. After approval and merge, let the apply job run from the protected default branch. Confirm that the apply job finishes and that the Terraform states list shows a new state version with the expected timestamp and author. Keep the first change deliberately small so that any problem with the backend, token or role is easy to isolate. The step is complete when the state version increments and a second plan shows no changes.

Pipeline patterns for multiple environments and teams

Giving each environment its own GitLab project keeps state, variables, protected branches and runner access separate. A production project can be restricted to a small group of Maintainers and Owners, while development projects stay open to the wider platform team. The state for each project is written under its own state name, so a failed apply in one environment does not hold a lock that blocks another.

  • Access boundaries: Writing or locking state requires the Maintainer or Owner role, so membership of each project defines who can change that environment.
  • Lock scope: Locks apply per state, so parallel pipelines in different projects do not queue behind each other.
  • Duplicated pipeline code: Shared CI configuration needs to be included rather than copied, or the environments drift apart.
  • Variable scope: Credentials for cloud accounts should be defined on the project or environment that uses them, not at group level.
  • Test to run: Confirm that a Developer in one project cannot read or write the state of another, and that a stale lock from one project does not affect the rest.

Before committing to this layout, check that your identity model and group structure can express the separation you want, and ask your partner to document the role mapping.

Security and operational risks to plan for

GitLab-managed state puts sensitive infrastructure data, encryption keys and write access inside the same platform that runs your pipelines. The cards below cover the controls to decide before the first apply, and they map to NIST SP 800-53 control families and the GitLab hardening guidance, referenced by title.

Protect db_key_base and its backups

GitLab encrypts stored state with a key derived from db_key_base, so anyone holding that secret and a database backup can read state. Confirm where the secret lives, who can read it, and that it is backed up with the rest of the instance secrets. Losing it makes existing state unreadable, so test a restore before you rely on the setup. Review the GitLab backup and restore documentation for how secrets are handled.

Assign locking and write roles deliberately

Writing or locking state requires the Maintainer or Owner role, so every project with state is a control point. Review which users hold those roles, remove standing access for people who only plan, and align the assignments with separation of duties in your change process. Map this to the access control families in NIST SP 800-53 and the authentication and authorization guidance in the GitLab hardening documentation.

Isolate runners and CI variables

Pipelines that run tofu apply need cloud credentials, and those credentials often sit in CI/CD variables that any job on the project can read. Use protected and masked variables, restrict which branches can run apply jobs, and run jobs on runners dedicated to infrastructure work. Confirm that shared runners cannot reach your provider accounts and that runner executors are reset between jobs.

Treat state as sensitive data

State files record resource attributes, and those can include generated passwords, connection strings, private keys and endpoint details, even when your configuration marks variables as sensitive. Keep state out of logs and artifacts, avoid printing outputs in job logs, and decide which project members can see the state file through the API. Plan OpenTofu state encryption for any sensitive content that must be protected beyond the platform default.

Audit who changed state

State versions show what changed, but they do not explain who approved the change or why. Pair state history with merge request approvals, pipeline logs and your audit events, and decide how long you keep each record. Confirm which audit events your tier provides and whether they stream to your SIEM, since auditors will ask for evidence that a given apply was authorized.

Recover from a bad apply

Because state is versioned, you can roll back to an earlier state version, but a rollback only restores what Terraform tracks. Resources already changed in the cloud still need reconciliation, and some changes cannot be reversed by restoring state. Write a runbook that names who can roll back, how you verify the environment afterward, and which changes need a fresh plan rather than a restore.

Moving existing Terraform state into GitLab safely

Back up state and code before anything else. The OpenTofu migration overview lists four steps for moving from Terraform: back up state and code, install OpenTofu, initialize and verify the configuration, and test with a small change. The overview describes the process as designed to be safe and reversible, but that holds only if the backup is complete and stored outside the working directory you are about to change. Copy the current state file, keep the version history of the configuration repository, and record which Terraform version last applied the state. Before you point the backend at GitLab, confirm which team members hold the Maintainer or Owner role on the project, because writing or locking state requires one of those roles and a migration blocked by missing permissions is a common stall point.

Install OpenTofu, initialize against the new GitLab-managed backend, and run a plan. Read the plan output closely. A clean plan with no unexpected creates, updates or destroys is the signal that state moved correctly. Then make one small, low-risk change, apply it, and confirm that the new state version appears in GitLab and that the lock releases after the run. Only after that should the pipeline be switched over. The OpenTofu migration overview adds a warning that matters here: configurations that share data through the terraform_remote_state data source typically need additional care during migration, and the overview links a separate guide for that case. Inventory every consumer of that data source before you move the producer, because a stack that reads outputs from a state file that has moved can fail in ways a plan on the producer will not reveal.

Plan for the cases the basic steps do not cover. Decide who is allowed to run the first apply after cutover, agree on a freeze window for changes to the affected stacks, and confirm that any CI jobs authenticate to GitLab with credentials scoped to the state they need. Ask your GitLab administrator to confirm how state versioning and locking behave on your instance version, and check the current OpenTofu release notes for any state-encryption configuration that must be in place before writing state. Keep the original backend configuration and the backed-up state until several successful applies have run from the new backend, so that reverting remains a documented option rather than an improvisation.

Limits to confirm in your environment

GitLab-managed state removes a separate backend, but it ties state to one GitLab instance, one GitLab version and one encryption design. The cards below cover the limits to confirm against your own versions, volumes and compliance requirements before you standardize on this approach.

OpenTofu and Terraform compatibility

OpenTofu aims to keep feature parity with Terraform, but the project states that compatibility with future Terraform releases is not guaranteed. If your organization still runs Terraform in some teams, confirm which Terraform features your configurations use and test them against the OpenTofu version you pin. Plan a review step for each Terraform upgrade you choose to follow.

Pinned OpenTofu client version

The current OpenTofu release is v1.13.1, released October 1, 2026. Pipelines and developer workstations should pin the same client version, because state written by a newer release may not be readable by an older one. Confirm how your pipeline images and the GitLab OpenTofu CI/CD component select the version, and record the pin in your change process.

GitLab version and upgrade stops

State lives inside your GitLab instance, so GitLab upgrades affect state access. GitLab requires upgrades to pass through required upgrade stops, and bug fixes are backported only to the current stable release. Plan each GitLab upgrade with the state feature in mind, and test a restore of state before an upgrade that crosses a stop.

State volume and locking patterns

Confirm that your number of state files, the frequency of plan and apply runs, and how often teams contend for the same state suit a single instance and its database. Role-based locking requires the Maintainer or Owner role to write or lock state, so review which people and service accounts hold those roles across projects. Large monorepo stacks with many concurrent pipelines warrant a sizing check with your GitLab administrators.

Encryption key design

GitLab encrypts state at rest with a key derived from db_key_base, which is part of the instance secrets. OpenTofu's own state encryption offers separate key providers, including AWS KMS, GCP KMS, OpenBao and Azure Vault, and it can be configured inside the OpenTofu code. Decide whether one instance secret is an acceptable key boundary for your policy, and confirm how db_key_base is backed up and rotated.

Provider and module coverage

The OpenTofu Registry lists more than 3,900 providers and 23,600 modules, searchable at search.opentofu.org. Coverage is not the same as support for your specific provider version or your internal modules. Check every provider and module your configurations depend on, including private registries and mirrors, against the OpenTofu version you pin, and confirm access from runners that sit inside your network.

Questions teams ask before moving state into GitLab

What drives the cost of moving state into GitLab?

Licensing is usually not the main cost, because GitLab-managed OpenTofu state is available in all tiers. The costs that change come from the work and the platform underneath it. One-time costs include migrating state and configurations, reworking pipelines to use the OpenTofu CI/CD component, and staff time for access and review. Recurring costs include running the GitLab instance, runner capacity, upgrades and backup storage. Instance sizing depends on how many users and pipelines share the instance, so start from your current GitLab load. When you request a quote, ask for the work broken out by state count, pipeline count, runner changes, and key and backup handling so you can compare it with your own team's effort.

Can our own platform team move state into GitLab without help?

Often yes, if your team already runs self-managed GitLab and maintains OpenTofu or Terraform pipelines. The migration follows a short sequence in the OpenTofu migration guide: back up state and code, install OpenTofu, initialize and verify the configuration, then test with a small change. The guide describes this process as designed to be safe and reversible, but each stack still needs a named owner. Configurations that share data through the terraform_remote_state data source need extra care, because consumers must move in step with the stacks they read from. Teams short on GitLab administration or pipeline experience often hand pipeline design and cutover planning to a partner while keeping ownership of state and approvals in-house. Ask for that split in writing.

What access and decisions do we need to provide?

Start with roles. Writing or locking state requires the Maintainer or Owner role, so name who will hold that role in each project and confirm it fits your separation of duties. Then provide access to the current state backend and its credentials, an inventory of stacks and the teams that depend on them, a change window for each cutover, and a decision on who approves pipeline changes and who can read CI variables. GitLab encrypts stored state with a key derived from db_key_base, so the person responsible for backups must know where that secret is kept and how it is restored. Agree these items before the work starts, because they gate every cutover.

How long does moving state into GitLab take?

The sources available here do not give a duration, so ask for one in the written scope and check it against your own inventory. The main drivers are the number of state files and workspaces, how many configurations read another stack's outputs through terraform_remote_state, how complex the pipelines are, and how many change windows your business allows. A small set of independent stacks moves faster than a shared network or identity stack that many teams depend on. Ask the implementer to list each stack, its dependents, its cutover method and the test change used to verify it. Those four items let you judge whether a proposed timeline is realistic for your environment.

Does GitLab-managed state replace a separate backend like S3?

Yes, for the stacks you move. GitLab-managed state is a remote backend hosted inside your GitLab instance, so it can take over the job an S3 bucket or similar backend does today. Decide stack by stack. Teams with heavy dependencies on an existing backend may move those stacks last, and some keep a separate backend for shared, long-lived stacks. Before moving the stacks that matter most, check that your GitLab availability, backup schedule and key handling meet your recovery requirements. The GitLab OpenTofu state documentation describes the setup and the roles each action needs.

What happens to encrypted state if db_key_base is lost?

Treat db_key_base as a secret that recovery depends on. GitLab encrypts stored state with the Lockbox gem using a key derived from db_key_base, so the secret must be backed up with the instance and restored with it. Confirm the exact backup and restore steps for your GitLab version in the GitLab backup and restore documentation before cutover, and include the secret in your recovery test. Ask the implementer how the secret is stored, who can read it and how it would be rotated, since a rotation can affect access to existing state. Plan a restore test and verify that the backup contains what you expect.

Will OpenTofu stay compatible with future Terraform releases?

Not guaranteed. OpenTofu is licensed under the Mozilla Public License 2.0 and describes itself as a drop-in replacement for Terraform that preserves existing workflows and configurations. Its FAQ aims to keep feature parity with Terraform, but it also states that compatibility with future Terraform releases is not guaranteed, because the community decides which features OpenTofu adopts. For that reason, pin the OpenTofu version in your pipelines, read the release notes before each upgrade, and run a small test change after every version bump. The latest release listed in the sources is v1.13.1, released on October 1, 2026.

How DeployOpen scopes and delivers GitLab state implementations

DeployOpen plans, implements, documents and supports self-hosted open-source platforms inside your own infrastructure, including GitLab-managed OpenTofu and Terraform state. We start from a written scope that names the state projects, the roles and the pipeline behavior, then build and verify the setup in your environment before handing it over.

How we scope

  • Inventory every configuration that uses a remote backend and which teams own each state file.
  • Map which projects need Maintainer or Owner rights to write or lock state, and document the approval path.
  • Agree in writing whether the state backend, pipeline component, migration and handover are in scope.

How we build

  • Configure the GitLab-managed state backend and run a test migration on a non-production configuration first.
  • Wire the OpenTofu CI/CD component into pipelines and confirm plan and apply jobs lock and release state correctly.
  • Test locking, state versioning and recovery of a prior state version before any production configuration moves.

How we hand over

  • Deliver runbooks for migration steps, state rollback, lock recovery and role assignments in your own documentation set.
  • Walk your platform and security engineers through the pipeline, the access model and the verification results.
  • Record every configuration change so your team can reproduce the setup in a new GitLab instance.

How we support

  • Offer ongoing support, upgrades and continuous testing as a retainer after the fixed-scope engagement ends.
  • Review OpenTofu and GitLab version changes against your pipelines before you upgrade either component.
  • Ask any partner for a quote built from the written scope, since DeployOpen does not publish a price list.

Sources and further reading

Talk to our team.

Tell us what you're working on, whether it's a deployment, an audit, a security test or a cyber range. You'll speak with an engineer who can help you scope it.

  • 30-minute call: free, with no obligation.
  • NDA on request: we can sign before you share details.
  • Clear next steps: a scope and plan after the call.