Skip to content

Remote state and layer contracts

Each root declares backend "http" {}. URLs and credentials are injected during the job, without hardcoding a project ID or password in HCL. States live in the terraform-modules project; the local root directory varies by provider and layer.

Names and addresses

TF_ROOT       = <CLOUD_PROVIDER>/<STACK>
TF_STATE_NAME = <ENVIRONMENT>-<CLOUD_PROVIDER>-<STACK>
STATE_URL     = <CI_API_V4_URL>/projects/<CI_PROJECT_ID>/terraform/state/<TF_STATE_NAME>

For an environment whose approved name is dev:

Layer AWS GCP
Network dev-aws-network dev-gcp-network
Kubernetes dev-aws-kubernetes dev-gcp-kubernetes
Database dev-aws-database dev-gcp-database
Platform dev-aws-platform dev-gcp-platform

The snapshot default is pro, not dev; the table illustrates the pattern. Choosing a different name does not migrate resources between states. It also does not automatically change cloud names, region, credentials, tags or network ranges.

Two authentication paths

Use Username Credential Configuration
Read/write the current root's state gitlab-ci-token CI_JOB_TOKEN TF_HTTP_*
Read outputs from other roots oauth2 TF_STATE_TOKEN TF_VAR_<layer>_state_* and terraform_remote_state

The current backend sets /lock endpoints, POST for locking, DELETE for unlocking, and a 5-second minimum retry wait. The pipeline also supplies all four state names to every job.

TF_STATE_TOKEN must be a backend-supported credential with access to the required states. Its exact type, scope and permissions are not recorded in the repository and must be checked in GitLab. Publishing its value is unnecessary to document or reproduce the configuration pattern. GitLab describes backend behavior and permissions in Terraform/OpenTofu state.

Historical names remain as aliases: eks_state_* points to AWS kubernetes state; rds_state_* to database; gke_state_* to GCP kubernetes. These are variable aliases, not duplicate states.

Output contracts

Consumer States read Data used
AWS Kubernetes network VPC and private subnets
AWS database network, kubernetes through eks VPC, subnets and node security group
AWS platform network, kubernetes through eks, database through rds VPC for ALB, cluster endpoint/CA/OIDC/name, DB connection
GCP Kubernetes network VPC, subnet and secondary range names
GCP database network VPC for Private Service Access and SQL
GCP platform network, kubernetes through gke, database Declares a network-state read; cluster endpoint/CA and DB connection

Output names form an API between roots. Renaming an output requires updating consumers and sequencing the change. An empty or not-yet-created state prevents the next layer from resolving references, which is why initial creation is sequential.

Using terraform_remote_state also requires protecting the complete state source. Marking an output sensitive changes some output presentation; it does not turn the remainder of state into a granular-access secret API.

Concurrency and stale plans

Each job uses resource_group: <environment>-<provider>-<layer> as well as backend locking. These controls complement each other: one serializes GitLab jobs, while the other protects state operations. They do not lock plan → manual wait → apply as one transaction.

If another pipeline changes the same layer while a plan awaits approval, the saved plan may no longer be applicable. Regenerate and review it rather than trying to apply it to a different state. The exceptional GCP job running state rm also changes state and can invalidate existing plans.

The four states for a provider are not an atomic transaction. A platform failure may leave networking, Kubernetes and the database correctly created. Diagnosis starts by identifying the last applied layer and which states contain resources.

Secrets, plans and artifacts

The database receives a password; platform roots create Kubernetes secrets and Helm releases containing tokens. Their values can participate in state and plans. The pipeline retains plan.cache/plan.json or destruction equivalents for one week and sets neither artifacts:access nor OpenTofu encryption.

JSON is generated by tofu show -json, so it must not be assumed to be a secret-free view. For reuse, restrict artifact and state access, review pipeline visibility, define retention/encryption and treat state backups as sensitive material. These are proposed operational improvements, not controls claimed to be deployed. GitLab explicitly documents the sensitivity of these files.

This documentation repository includes no real states, plans, credentials or secret copies.

Recovery and namespace changes

Before restoring or editing state, identify the project, state name, lineage/serial, provider and expected resources; stop concurrent operations and keep a protected backup. Restoring state does not restore SQL data or automatically recover deleted cloud objects.

Changing the group's display name does not change state names. If the group path changes, backend URLs built using CI_PROJECT_ID remain based on the project's numeric ID; agent paths, includes and repository references are a separate concern. If the project is recreated or imported with a different ID, copying the source does not automatically bring its states across.

See agent access, the pipeline and destruction and recovery.

Sources