Skip to main content

Backend Provisioning

provision.backend automatically creates the Terraform state backend (an S3 bucket, or an Azure resource group + storage account + container) before the first run of a component. The backend: block describes where state lives; provision.backend.enabled tells Atmos to create that location if it doesn't exist. Use this to eliminate the chicken-and-egg bootstrap problem of needing a state backend before you can deploy the component that creates it.

Experimental

Schema​

provision.backend.enabled

Automatically provision the Terraform state backend before the first run. Supports S3 backends (with optional native S3 locking, Terraform 1.10+) and azurerm backends (resource group, storage account, and blob container).

  • Type: boolean
  • Default: false
  • Applies to: Terraform components only

Configuration​

Enable automatic backend creation alongside the existing backend: configuration.

stacks/catalog/vpc.yaml
components:
terraform:
vpc:
backend_type: s3
backend:
bucket: acme-ue1-dev-tfstate
key: vpc/terraform.tfstate
region: us-east-1
use_lockfile: true # Native S3 locking (Terraform 1.10+)

provision:
backend:
enabled: true # Auto-create the bucket if missing

When enabled, before running any Terraform command Atmos will:

  1. Check whether the configured backend already exists.
  2. If it doesn't, provision it using secure defaults (versioning, encryption, access blocking).
  3. Continue with terraform init and execution.

Configuration Hierarchy​

provision.backend participates in Atmos's deep-merge system, so you can set defaults at high levels and override per component.

Toolchain-Level Defaults​

Apply backend provisioning to every Terraform component in a stack by setting it under the toolchain key:

stacks/orgs/acme/plat/dev/_defaults.yaml
terraform:
provision:
backend:
enabled: true # Every Terraform component in this stack gets backend provisioning

Environment-Specific Overrides​

Configure different provisioning policies per environment:

stacks/orgs/acme/plat/staging/_defaults.yaml
terraform:
provision:
backend:
enabled: false # Override for specific environment

Catalog Inheritance​

Share provision configuration through reusable catalog components:

stacks/catalog/networking/vpc.yaml
components:
terraform:
vpc/defaults:
provision:
backend:
enabled: true # Catalog default

# stacks/dev.yaml
components:
terraform:
vpc:
metadata:
inherits: [vpc/defaults]
# Inherits provision.backend.enabled: true

Component-Level Overrides​

Opt a single component out of a toolchain or catalog default by setting enabled: false:

stacks/orgs/acme/plat/dev/us-east-1.yaml
components:
terraform:
legacy-vpc:
provision:
backend:
enabled: false # Use a manually-bootstrapped backend

Atmos combines configurations from all levels, with component-level configuration having the highest precedence. Use catalog inheritance for reusable patterns and override per component when needed.

Supported Backend Types​

S3 (AWS)​

The S3 backend provisioner creates buckets with hardcoded security best practices:

  • Versioning: Enabled (protects against accidental deletions)
  • Encryption: AES-256 with AWS-managed keys (always enabled)
  • Public Access: Blocked (all 4 block settings enabled)
  • Locking: Native S3 locking (Terraform 1.10+, no DynamoDB required)
  • Tags: Automatic resource tags (Name, ManagedBy=Atmos)

Required Configuration:

stacks/catalog/vpc.yaml
backend_type: s3
backend:
bucket: my-terraform-state # Required
key: vpc/terraform.tfstate
region: us-east-1 # Required
use_lockfile: true # Enable native S3 locking (Terraform 1.10+)

provision:
backend:
enabled: true

Cross-Account Provisioning​

stacks/catalog/vpc.yaml
backend:
bucket: my-terraform-state
region: us-east-1
use_lockfile: true # Enable native S3 locking (Terraform 1.10+)
assume_role:
role_arn: arn:aws:iam::999999999999:role/TerraformStateAdmin

provision:
backend:
enabled: true

The provisioner will assume the specified role to create the bucket in the target account.

Azure (azurerm)​

The azurerm backend provisioner creates the resource group (if missing), storage account, and blob container with hardcoded security best practices:

  • Blob versioning: Enabled (the direct analog of S3 versioning — every state write is recoverable)
  • Soft delete: Enabled for blobs and containers (30-day retention)
  • Transport: TLS 1.2 minimum, HTTPS-only
  • Public access: Blocked (AllowBlobPublicAccess=false); the state container is private
  • Entra ID hardening: When the backend sets use_azuread_auth: true, a newly created storage account gets shared-key access disabled (no account keys to leak). This applies only at creation time — an existing storage account retains its shared-key setting. If its backend is incomplete, Atmos can still apply blob data protection (versioning + soft delete) and create the missing container. Harden a pre-existing account separately (e.g. set allowSharedKeyAccess: false via the module or az storage account update).
  • Tags: Automatic resource tags (Name, ManagedBy=Atmos)

Locking: No lock resource is created. The azurerm backend serializes concurrent state writes using native Azure Blob Storage blob leases — the equivalent of native S3 locking / DynamoDB, built into Blob Storage.

Required Configuration:

stacks/catalog/vpc.yaml
backend_type: azurerm
backend:
resource_group_name: rg-tfstate-cus # Required
storage_account_name: sttfstateplatcus # Required (globally unique, 3-24 lowercase alphanumeric)
container_name: tfstate # Required
key: vpc.terraform.tfstate
use_azuread_auth: true # Recommended — disables shared-key access

provision:
backend:
enabled: true

The Azure location is not a valid azurerm backend argument, so it is taken from the active Azure identity (or inherited from the resource group when it already exists) — not from the backend: block. The subscription comes from backend.subscription_id if set, otherwise from the active Azure identity.

NOTE:

The auto-provisioned account uses secure-but-simple defaults (Standard_LRS, Microsoft-managed keys, public network access enabled with Entra ID / RBAC gating). It is intended for dev/test/bootstrap, not production. For production, manage state storage with a full module (e.g. Azure/avm-res-storage-storageaccount) to add customer-managed keys, private endpoints, network ACLs, and redundancy. The provisioner is idempotent, so you can import the resources into a module later and keep provision.backend.enabled: true.

Manual Provisioning​

You can also provision backends explicitly using the CLI:

# Provision backend before Terraform execution
atmos terraform backend create vpc --stack dev

# Then run Terraform
atmos terraform apply vpc --stack dev

This is useful for:

  • CI/CD pipelines with separate provisioning stages
  • Troubleshooting provisioning issues
  • Batch provisioning for multiple components
  • Pre-provisioning before large-scale deployments

See atmos terraform backend for complete CLI documentation.

Required IAM Permissions​

For S3 backend provisioning, the identity needs these permissions:

IAM Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:HeadBucket",
"s3:PutBucketVersioning",
"s3:PutBucketEncryption",
"s3:PutBucketPublicAccessBlock",
"s3:PutBucketTagging"
],
"Resource": "arn:aws:s3:::my-terraform-state*"
}
]
}

For cross-account provisioning, also add:

IAM Policy
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::999999999999:role/TerraformStateAdmin"
}

Solving the Terraform Bootstrap Problem​

Automatic provisioning is fully compatible with Terraform-managed backends. It solves a classic chicken-and-egg problem: "How do I manage my state backend with Terraform when I need that backend to exist before Terraform can run?"

Traditional Workaround:

  1. Use local state temporarily.
  2. Create the S3 bucket with Terraform using local state.
  3. Switch backend configuration to S3.
  4. Import the bucket into the S3-backed state.
  5. Delete local state files.

With Atmos Automatic Provisioning:

  1. Enable provision.backend.enabled: true.
  2. Run atmos terraform plan — backend auto-created with secure defaults.
  3. Import the bucket into Terraform (no local state dance needed).
  4. Done — everything managed by Terraform.

Migrating to Terraform-Managed Backends​

Once your backend is provisioned, you can import it into Terraform for advanced management.

Step 1: Provision the Backend

Use Atmos to create the backend with secure defaults:

atmos terraform backend create vpc --stack prod

Step 2: Import into Terraform

Add the backend to your Terraform configuration and import it:

components/terraform/backend-state/main.tf
# Import the provisioned backend
import {
to = aws_s3_bucket.terraform_state
id = "acme-ue1-prod-tfstate"
}

resource "aws_s3_bucket" "terraform_state" {
bucket = "acme-ue1-prod-tfstate"
}

# Add lifecycle rules
resource "aws_s3_bucket_lifecycle_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id

rule {
id = "delete-old-versions"
status = "Enabled"

noncurrent_version_expiration {
noncurrent_days = 90
}
}
}

# Add replication for disaster recovery
resource "aws_s3_bucket_replication_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
role = aws_iam_role.replication.arn

rule {
id = "replicate-state"
status = "Enabled"

destination {
bucket = aws_s3_bucket.terraform_state_replica.arn
storage_class = "STANDARD_IA"
}
}
}

Step 3: Optionally Disable Automatic Provisioning

Once Terraform manages the backend, you can optionally disable automatic provisioning:

stacks/prod.yaml
provision:
backend:
enabled: false # Backend now managed by Terraform
NOTE:

You can leave provision.backend.enabled: true even after importing to Terraform. The provisioner is idempotent — it will detect the bucket exists and skip creation, causing no conflicts with Terraform management.

Alternatively, use the terraform-aws-tfstate-backend module for backends with advanced features like cross-region replication, lifecycle policies, and custom KMS keys.

Idempotent Operations​

Backend provisioning is idempotent — running it multiple times is safe:

$ atmos terraform backend create vpc --stack dev
✓ Created S3 bucket 'acme-ue1-dev-tfstate'

$ atmos terraform backend create vpc --stack dev
S3 bucket 'acme-ue1-dev-tfstate' already exists (idempotent)
✓ Backend provisioning completed

Global Defaults​

Backend provisioning is opted in per component or per toolchain because it has stronger side effects (creating cloud resources). Like the workdir default, a stack-wide default belongs in the toolchain section (terraform.provision) of a shared stack manifest, not in atmos.yaml settings.