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¶
- Backend, HTTP variables and CI aliases.
- AWS Kubernetes contract, database, platform.
- GCP Kubernetes contract, database, platform.
- AWS network outputs, EKS, RDS, GCP network, GKE, Cloud SQL.
- Thesis §7.2.7, printed page 47 (PDF 53), figure 10: GitLab-persisted states. Concurrency guarantees and isolation boundaries are inferred from the cited implementation.