State Management
Every IaC deployment tracks state -- the mapping between what your manifest declares and what actually exists in the cloud. State enables OpenMCF to know what has changed between deployments, what needs to be created, updated, or destroyed, and what the current outputs of a deployment are.
How state is stored depends on which IaC engine you use. Pulumi and OpenTofu/Terraform have different backend systems, and OpenMCF configures them through manifest labels.
Pulumi State Backends
Pulumi organizes state by stack -- a named instance of a deployment identified by a fully qualified domain name (FQDN) in the format {organization}/{project}/{stack-name}.
Configuring Pulumi State
Pulumi state configuration is provided through manifest labels:
metadata:
labels:
openmcf.org/provisioner: pulumi
pulumi.openmcf.org/organization: acme
pulumi.openmcf.org/project: platform
pulumi.openmcf.org/stack.name: production.KubernetesPostgres.session-store
Or as a single FQDN using the --stack flag:
openmcf pulumi up -f postgres.yaml --stack acme/platform/production
Supported Pulumi Backends
| Backend | Description |
|---|---|
| Pulumi Cloud | Managed state storage with history, audit trail, and team collaboration. Default backend. |
| S3 | Self-managed state in an AWS S3 bucket. |
| GCS | Self-managed state in a Google Cloud Storage bucket. |
| Azure Blob | Self-managed state in Azure Blob Storage. |
| Local | State stored on the local filesystem. Suitable for development only. |
The Pulumi backend is configured through Pulumi's standard mechanisms (environment variables, pulumi login, or configuration files). OpenMCF's role is to set the stack FQDN from the manifest labels, not to configure the backend connection itself.
OpenTofu/Terraform State Backends
OpenTofu and Terraform use a backend block in the Terraform configuration to determine where state is stored. OpenMCF generates this configuration from manifest labels.
Configuring Tofu/Terraform State
State backend configuration is provided through manifest labels with the tofu.openmcf.org/backend.* prefix:
metadata:
labels:
openmcf.org/provisioner: tofu
tofu.openmcf.org/backend.type: s3
tofu.openmcf.org/backend.bucket: my-tfstate-bucket
tofu.openmcf.org/backend.key: prod/postgres/terraform.tfstate
tofu.openmcf.org/backend.region: us-east-1
The CLI reads these labels, writes a backend.tf file in the workspace, and passes the configuration to tofu init.
Legacy terraform.openmcf.org/backend.* labels are also supported for backward compatibility.
Supported Backends and Required Fields
S3
| Label | Required | Description | Example |
|---|---|---|---|
backend.type | Yes | Must be s3 | s3 |
backend.bucket | Yes | S3 bucket name | my-terraform-state-bucket |
backend.key | Yes | State file path within bucket | env/prod/terraform.tfstate |
backend.region | Yes | AWS region (or auto for S3-compatible) | us-west-2 |
backend.endpoint | Only if region=auto | Custom S3-compatible endpoint | https://acct.r2.cloudflarestorage.com |
GCS
| Label | Required | Description | Example |
|---|---|---|---|
backend.type | Yes | Must be gcs | gcs |
backend.bucket | Yes | GCS bucket name | my-terraform-state |
backend.key | Yes | Prefix path within bucket | terraform/state |
Azure Storage (azurerm)
| Label | Required | Description | Example |
|---|---|---|---|
backend.type | Yes | Must be azurerm | azurerm |
backend.bucket | Yes | Azure Storage container name | tfstate |
backend.key | Yes | State file blob name | prod.terraform.tfstate |
Local
| Label | Required | Description |
|---|---|---|
backend.type | Yes | Must be local |
No other fields are required for local backends. State is stored in the workspace directory.
S3-Compatible Backends (Cloudflare R2, MinIO)
OpenMCF supports S3-compatible backends for teams using Cloudflare R2, MinIO, or other S3-compatible object stores. To use an S3-compatible backend, set the region to auto and provide the custom endpoint:
metadata:
labels:
tofu.openmcf.org/backend.type: s3
tofu.openmcf.org/backend.bucket: my-r2-state
tofu.openmcf.org/backend.key: prod/terraform.tfstate
tofu.openmcf.org/backend.region: auto
tofu.openmcf.org/backend.endpoint: https://your-account-id.r2.cloudflarestorage.com
When region=auto is detected, the CLI automatically configures the S3 backend with the compatibility flags that S3-compatible backends require.
State Lifecycle
Both engines follow the same conceptual lifecycle:
| Phase | Pulumi Command | Tofu/Terraform Command | What Happens |
|---|---|---|---|
| Initialize | openmcf pulumi init | openmcf tofu init | Set up backend, download providers |
| Preview | openmcf pulumi preview | openmcf tofu plan | Compare manifest against state, show planned changes |
| Apply | openmcf pulumi up | openmcf tofu apply | Execute changes, update state |
| Refresh | openmcf pulumi refresh | openmcf tofu refresh | Sync state with actual cloud resources |
| Destroy | openmcf pulumi destroy | openmcf tofu destroy | Delete all resources, clean up state |
The preview/plan step is where state management provides its key value: by comparing your manifest against the stored state, the engine can tell you exactly what will change before you commit to the deployment.
Multi-Environment State Isolation
Each environment should have its own isolated state. In Pulumi, this is achieved through different stack names:
# Production
pulumi.openmcf.org/stack.name: production.KubernetesPostgres.session-store
# Staging
pulumi.openmcf.org/stack.name: staging.KubernetesPostgres.session-store
In OpenTofu/Terraform, this is achieved through different state file keys:
# Production
tofu.openmcf.org/backend.key: production/postgres/terraform.tfstate
# Staging
tofu.openmcf.org/backend.key: staging/postgres/terraform.tfstate
Both approaches ensure that a deployment to staging never reads or modifies the production state, and vice versa.
What's Next
- Dual IaC Engines -- How the Pulumi and OpenTofu/Terraform engines work
- Manifests -- How manifest labels configure state backends
- Module System -- How IaC modules are resolved before state operations run
Next article