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:
| Backend | Best For | Cost | Collaboration | Encryption |
|---|---|---|---|---|
| Pulumi Cloud (default) | Teams, production | Free tier + paid | Built-in role-based access | Service-managed or KMS |
| AWS S3 | Self-hosted, cost-conscious | $0.023 per GB/month | Requires DynamoDB for locking | KMS, can use customer-managed keys |
| Azure Blob Storage | Azure-centric orgs | $0.018 per GB/month | Requires Blob lease for locking | AES-256, customer-managed keys available |
| Google Cloud Storage | GCP-centric orgs | $0.020 per GB/month | Requires Google Cloud Datastore | Customer-managed keys available |
| Local file system | Development only | Free | Not suitable for teams | None (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
Option 1: Pulumi Cloud (Recommended)
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.