Skip to main content

Pulumi State Management: Backends and Practices

Pulumi state is the source of truth for your deployed infrastructure: it maps the resources declared in your code to their actual cloud IDs, tracks changes, and enables diffing before deployment. State files are large (often megabytes) and contain sensitive data (encryption keys, database passwords), so storing them securely and sharing them properly among team members is critical. This article covers state backends (where state lives), backup strategies, and best practices for multi-person teams.

What Pulumi State Contains

A Pulumi state file is a JSON document mapping resources to cloud IDs. Example:

{
"version": 3,
"serial": 42,
"lineage": "my-infra-prod",
"resources": [
{
"urn": "urn:pulumi:prod::my-infra::aws:ec2/securityGroup:SecurityGroup::app-sg",
"type": "aws:ec2/securityGroup:SecurityGroup",
"custom": true,
"id": "sg-0a1b2c3d4e5f6g7h8",
"outputs": {
"arn": "arn:aws:ec2:us-east-1:123456789:security-group/sg-0a1b2c3d4e5f6g7h8"
}
},
{
"urn": "urn:pulumi:prod::my-infra::aws:rds/instance:Instance::app-db",
"type": "aws:rds/instance:Instance",
"custom": true,
"id": "app-db",
"outputs": {
"endpoint": "app-db.c123456789.us-east-1.rds.amazonaws.com",
"password": "ENCRYPTED[...]" # Secrets are encrypted
}
}
]
}

Each resource has a URN (Uniform Resource Name) uniquely identifying it and an ID linking it to the actual cloud resource. Deleting a resource from the state file tells Pulumi to delete the cloud resource; this is how pulumi destroy works.

State Backends: Where State Lives

Pulumi supports multiple backends for storing state:

BackendBest ForCostCollaborationEncryption
Pulumi Cloud (default)Teams, productionFree tier + paidBuilt-in role-based accessService-managed or KMS
AWS S3Self-hosted, cost-conscious$0.023 per GB/monthRequires DynamoDB for lockingKMS, can use customer-managed keys
Azure Blob StorageAzure-centric orgs$0.018 per GB/monthRequires Blob lease for lockingAES-256, customer-managed keys available
Google Cloud StorageGCP-centric orgs$0.020 per GB/monthRequires Google Cloud DatastoreCustomer-managed keys available
Local file systemDevelopment onlyFreeNot suitable for teamsNone (unencrypted)

Pulumi Cloud (Default)

Pulumi Cloud is a managed backend provided by Pulumi. It handles versioning, backups, and access control. Free for personal use; teams require a paid account.

# Authenticate with Pulumi Cloud
pulumi login

# Your state is now stored on Pulumi Cloud (encrypted)
pulumi up

View your stacks and state on the Pulumi Cloud dashboard: https://app.pulumi.com.

AWS S3 Backend

For self-hosted setups, store state in S3 with a DynamoDB table for locking (so concurrent deployments don't corrupt state).

# Create S3 bucket and DynamoDB table
aws s3 mb s3://pulumi-state-prod-us-east-1
aws dynamodb create-table \
--table-name pulumi-state-lock \
--attribute-definitions AttributeName=LockID,AttributeType=S \
--key-schema AttributeName=LockID,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

# Configure Pulumi to use S3
pulumi login s3://pulumi-state-prod-us-east-1

# Verify
pulumi stack ls

In Pulumi.yaml, optionally configure the S3 backend explicitly:

name: my-infra
runtime: python
config:
aws:region: us-east-1
aws:profile: default
backend:
url: s3://pulumi-state-prod-us-east-1

S3 state files are not encrypted by default. Enable encryption:

# Enable S3 bucket encryption
aws s3api put-bucket-encryption \
--bucket pulumi-state-prod-us-east-1 \
--server-side-encryption-configuration '{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "AES256"
}
}
]
}'

# Or use a customer-managed KMS key for finer control
aws s3api put-bucket-encryption \
--bucket pulumi-state-prod-us-east-1 \
--server-side-encryption-configuration '{
"Rules": [
{
"ApplyServerSideEncryptionByDefault": {
"SSEAlgorithm": "aws:kms",
"KMSMasterKeyID": "arn:aws:kms:us-east-1:123456789:key/12345678"
}
}
]
}'

# Block public access
aws s3api put-public-access-block \
--bucket pulumi-state-prod-us-east-1 \
--public-access-block-configuration \
"BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# Enable versioning for disaster recovery
aws s3api put-bucket-versioning \
--bucket pulumi-state-prod-us-east-1 \
--versioning-configuration Status=Enabled

Now your state is encrypted at rest, versioned, and protected from accidental deletion.

Accessing State Across Teams

Pulumi Cloud handles team access via roles:

# Invite team members to your Pulumi organization
pulumi org add-member [email protected]
pulumi org add-member [email protected]

# Members log in with their Pulumi account
pulumi login

# Access shared stacks
pulumi stack select organization/my-infra/prod
pulumi up

Pulumi Cloud provides role-based access control: admins, members, and viewers with granular permissions.

Option 2: S3 + IAM

Store state in S3 and control access via IAM policies:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789:user/alice"
},
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": "arn:aws:s3:::pulumi-state-prod-us-east-1/*"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789:user/alice"
},
"Action": [
"dynamodb:DescribeTable",
"dynamodb:UpdateItem",
"dynamodb:GetItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789:table/pulumi-state-lock"
}
]
}

Team members configure S3 as their backend:

pulumi login s3://pulumi-state-prod-us-east-1

State File Locking and Concurrent Deployments

If two team members run pulumi up simultaneously, state corruption can occur. Pulumi uses backends with locking mechanisms to prevent this.

Pulumi Cloud: Automatic locking; Pulumi Cloud serializes deployments.

S3: Uses DynamoDB lease locks. Example lease:

LockID: my-infra/prod
OperationPrincipal: arn:aws:iam::123456789:user/alice
TxnID: 12345678-1234-1234-1234-123456789

If Alice runs pulumi up and it hangs or crashes, the lock remains. Unlock manually with care:

# List locks
aws dynamodb scan --table-name pulumi-state-lock

# Delete a lock (only if you're certain the deployment is stuck)
aws dynamodb delete-item \
--table-name pulumi-state-lock \
--key '{"LockID": {"S": "my-infra/prod"}}'

Backup and Disaster Recovery

State files are irreplaceable. Always back them up.

Pulumi Cloud: Automatic backups with retention; no action needed.

S3: Enable versioning and replicate to another region:

# Versioning (already enabled above)
aws s3api get-bucket-versioning --bucket pulumi-state-prod-us-east-1

# Replication to another region
aws s3api put-bucket-replication \
--bucket pulumi-state-prod-us-east-1 \
--replication-configuration '{
"Role": "arn:aws:iam::123456789:role/s3-replication",
"Rules": [
{
"Status": "Enabled",
"Priority": 1,
"Prefix": "",
"Destination": {
"Bucket": "arn:aws:s3:::pulumi-state-backup-us-west-2",
"StorageClass": "STANDARD"
}
}
]
}'

For disaster recovery, test restoring state from backups annually.

Key Takeaways

  • Pulumi state maps infrastructure code to cloud resources; it's the source of truth.
  • State files contain sensitive data (passwords, keys) and must be encrypted and backed up.
  • Backends: Pulumi Cloud (easiest, recommended for teams), S3 (self-hosted), or local file (dev only).
  • Enable S3 encryption, versioning, and replication for production state.
  • Use state locking to prevent concurrent deployments from corrupting state.

Frequently Asked Questions

What happens if I lose my state file?

Pulumi can no longer track resources and will attempt to create duplicates if you re-run. For recovery, you must manually link existing resources back to state using pulumi import.

Can I migrate state from one backend to another?

Yes. Pull state from the old backend, push to the new: pulumi login <old-backend>; pulumi state pull > state.json; pulumi login <new-backend>; pulumi state push state.json.

Should I commit state files to Git?

Never commit local state files to Git. If using Pulumi Cloud or S3, don't commit the stack YAML files either (they contain stack metadata). Commit only your code and non-sensitive config.

What if state and cloud resources get out of sync?

Run pulumi refresh to reconcile state with actual cloud resources. Pulumi queries the cloud, updates state, and shows what changed.

How do I export state for auditing?

Use pulumi state pull to download state as JSON. Audit tools can parse and analyze it.

Further Reading