| name | pulumi-stacks |
| user-invocable | false |
| description | Use when managing multiple environments with Pulumi stacks for development, staging, and production deployments. |
| allowed-tools | ["Bash","Read"] |
Pulumi Stacks
Manage multiple environments and configurations with Pulumi stacks for consistent infrastructure across development, staging, and production.
Overview
Pulumi stacks are isolated, independently configurable instances of a Pulumi program. Each stack has its own state, configuration, and resources, enabling you to deploy the same infrastructure code to multiple environments.
Stack Basics
Creating and Selecting Stacks
pulumi new aws-typescript
pulumi stack init dev
pulumi stack ls
pulumi stack select dev
pulumi stack
pulumi stack rm dev
Stack Configuration
pulumi config set aws:region us-east-1
pulumi config set instanceType t3.micro
pulumi config set --secret dbPassword mySecurePassword123
pulumi config get aws:region
pulumi config
pulumi config rm instanceType
Stack Configuration Files
Pulumi.yaml (Project File)
name: my-infrastructure
user-invocable: false
runtime: nodejs
description: Multi-environment infrastructure
config:
aws:region:
description: AWS region for deployment
default: us-east-1
instanceType:
description: EC2 instance type
default: t3.micro
environment:
description: Environment name
Pulumi.dev.yaml (Stack Config)
config:
aws:region: us-east-1
my-infrastructure:instanceType: t3.micro
my-infrastructure:environment: development
my-infrastructure:minSize: "1"
my-infrastructure:maxSize: "3"
my-infrastructure:enableMonitoring: "false"
Pulumi.staging.yaml
config:
aws:region: us-east-1
my-infrastructure:instanceType: t3.small
my-infrastructure:environment: staging
my-infrastructure:minSize: "2"
my-infrastructure:maxSize: "5"
my-infrastructure:enableMonitoring: "true"
Pulumi.prod.yaml
config:
aws:region: us-west-2
my-infrastructure:instanceType: t3.medium
my-infrastructure:environment: production
my-infrastructure:minSize: "3"
my-infrastructure:maxSize: "10"
my-infrastructure:enableMonitoring: "true"
my-infrastructure:backupRetention: "30"
Reading Configuration in Code
TypeScript Configuration
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const instanceType = config.get("instanceType") || "t3.micro";
const environment = config.require("environment");
const minSize = config.getNumber("minSize") || 1;
const maxSize = config.getNumber("maxSize") || 3;
const enableMonitoring = config.getBoolean("enableMonitoring") || false;
const dbPassword = config.requireSecret("dbPassword");
const instance = new aws.ec2.Instance("web-server", {
instanceType: instanceType,
ami: "ami-0c55b159cbfafe1f0",
tags: {
Name: `web-server-${environment}`,
Environment: environment,
},
monitoring: enableMonitoring,
});
stackName = pulumi.();
instanceId = instance.;
Python Configuration
import pulumi
import pulumi_aws as aws
config = pulumi.Config()
instance_type = config.get("instanceType") or "t3.micro"
environment = config.require("environment")
min_size = config.get_int("minSize") or 1
max_size = config.get_int("maxSize") or 3
enable_monitoring = config.get_bool("enableMonitoring") or False
db_password = config.require_secret("dbPassword")
instance = aws.ec2.Instance(
"web-server",
instance_type=instance_type,
ami="ami-0c55b159cbfafe1f0",
tags={
"Name": f"web-server-{environment}",
"Environment": environment,
},
monitoring=enable_monitoring,
)
pulumi.export("stack_name", pulumi.get_stack())
pulumi.export("instance_id", instance.id)
Environment-Specific Resources
Conditional Resource Creation
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
const enableHighAvailability = config.getBoolean("enableHA") || false;
const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
tags: {
Name: `vpc-${environment}`,
Environment: environment,
},
});
const azCount = environment === "production" ? 3 : 1;
const subnets: aws.ec2.Subnet[] = [];
for (let i = 0; i < azCount; i++) {
const subnet = new aws.ec2.Subnet(`subnet-${i}`, {
vpcId: vpc.id,
cidrBlock: ,
: ,
: {
: ,
: environment,
},
});
subnets.(subnet);
}
: aws.. | ;
(environment === ) {
eip = aws..(, {
: ,
});
natGateway = aws..(, {
: eip.,
: subnets[].,
: {
: ,
: environment,
},
});
}
db = aws..(, {
: ,
: ,
: environment === ? : ,
: environment === ? : ,
: ,
: ,
: config.(),
: environment === ,
: environment === ? : ,
: environment !== ,
: {
: ,
: environment,
},
});
vpcId = vpc.;
subnetIds = subnets.( s.);
dbEndpoint = db.;
Stack References
Cross-Stack References
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const vpc = new aws.ec2.Vpc("shared-vpc", {
cidrBlock: "10.0.0.0/16",
tags: {
Name: "shared-vpc",
},
});
const subnet = new aws.ec2.Subnet("shared-subnet", {
vpcId: vpc.id,
cidrBlock: "10.0.1.0/24",
tags: {
Name: "shared-subnet",
},
});
export const vpcId = vpc.id;
export const subnetId = subnet.id;
export const vpcCidr = vpc.cidrBlock;
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const infraStack = new pulumi.StackReference("myorg/infra/prod");
const vpcId = infraStack.getOutput("vpcId");
const subnetId = infraStack.getOutput("subnetId");
const securityGroup = new aws.ec2.SecurityGroup("app-sg", {
vpcId: vpcId,
description: "Security group for application",
ingress: [{
protocol: "tcp",
fromPort: 80,
toPort: 80,
cidrBlocks: ["0.0.0.0/0"],
}],
});
const instance = new aws.ec2.Instance("app-server", {
instanceType: "t3.micro",
ami: "ami-0c55b159cbfafe1f0",
subnetId: subnetId,
vpcSecurityGroupIds: [securityGroup.],
: {
: ,
},
});
instanceIp = instance.;
Stack Reference Commands
cd infra
pulumi stack select prod
pulumi up
cd ../app
pulumi stack select prod
pulumi up
pulumi stack output --stack myorg/infra/prod
Stack Outputs
Exporting Stack Outputs
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
});
const bucket = new aws.s3.Bucket("app-bucket", {
bucket: `myapp-${environment}-bucket`,
});
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
dbName: "myapp",
username: "admin",
password: config.requireSecret("dbPassword"),
skipFinalSnapshot: true,
});
export const vpcId = vpc.;
vpcCidr = vpc.;
bucketName = bucket.;
bucketArn = bucket.;
dbEndpoint = db.;
dbPort = db.;
dbConnectionString = pulumi.;
stackName = pulumi.();
projectName = pulumi.();
region = aws.().( r.);
Accessing Stack Outputs
pulumi stack output
pulumi stack output vpcId
pulumi stack output --json
VPC_ID=$(pulumi stack output vpcId)
echo "VPC ID: $VPC_ID"
export $(pulumi stack output --json | jq -r 'to_entries[] | "\(.key)=\(.value)"')
Stack Transformations
Global Resource Transformations
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
pulumi.runtime.registerStackTransformation((args) => {
if (args.type.startsWith("aws:")) {
args.props.tags = {
...args.props.tags,
Environment: environment,
ManagedBy: "Pulumi",
Stack: pulumi.getStack(),
};
}
return {
props: args.props,
opts: args.opts,
};
});
const vpc = new aws.ec2.Vpc("main", {
cidrBlock: "10.0.0.0/16",
});
const bucket = new aws.s3.(, {
});
Resource-Specific Transformations
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
const enforceEncryption = (args: pulumi.ResourceTransformationArgs) => {
if (args.type === "aws:s3/bucket:Bucket") {
args.props.serverSideEncryptionConfiguration = {
rule: {
applyServerSideEncryptionByDefault: {
sseAlgorithm: "AES256",
},
},
};
}
if (args.type === "aws:rds/instance:Instance") {
args.props.storageEncrypted = true;
}
return {
props: args.props,
opts: args.opts,
};
};
pulumi.runtime.registerStackTransformation(enforceEncryption);
const bucket = new aws.s3.Bucket();
db = aws..(, {
: ,
: ,
: ,
});
Stack Tags and Organization
Tagging Strategy
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
const project = pulumi.getProject();
const stack = pulumi.getStack();
const commonTags = {
Project: project,
Environment: environment,
Stack: stack,
ManagedBy: "Pulumi",
CostCenter: config.get("costCenter") || "engineering",
Owner: config.get("owner") || "platform-team",
};
function mergeTags(resourceTags?: { [key: string]: string }): { [key: string]: string } {
return {
...commonTags,
...resourceTags,
};
}
const vpc = new aws.ec2.Vpc(, {
: ,
: ({
: ,
: ,
}),
});
bucket = aws..(, {
: ({
: ,
: ,
: ,
}),
});
db = aws..(, {
: ,
: ,
: ,
: ({
: ,
: ,
: ,
}),
});
Stack Import and Export
Exporting Stack State
pulumi stack export > stack-state.json
pulumi stack export --file stack-backup.json
pulumi stack export --show-secrets > stack-with-secrets.json
Importing Stack State
pulumi stack import --file stack-state.json
cat stack-state.json | pulumi stack import
Stack Migration
pulumi stack select old-stack
pulumi stack export --file old-stack.json
pulumi stack init new-stack
pulumi stack import --file old-stack.json
pulumi preview
Multi-Region Deployments
Region-Specific Stacks
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const awsConfig = new pulumi.Config("aws");
const region = awsConfig.require("region");
const environment = config.require("environment");
const vpc = new aws.ec2.Vpc(`vpc-${region}`, {
cidrBlock: "10.0.0.0/16",
tags: {
Name: `vpc-${environment}-${region}`,
Region: region,
Environment: environment,
},
});
const usEast1Provider = new aws.Provider("us-east-1", {
region: "us-east-1",
});
const certificate = new aws.acm.Certificate("cert", {
domainName: `.example.com`,
: ,
: {
: ,
: environment,
},
}, { : usEast1Provider });
deploymentRegion = region;
vpcId = vpc.;
certificateArn = certificate.;
Multi-Region Stack Configuration
config:
aws:region: us-east-1
my-app:environment: production
my-app:isPrimaryRegion: "true"
config:
aws:region: us-west-2
my-app:environment: production
my-app:isPrimaryRegion: "false"
config:
aws:region: eu-west-1
my-app:environment: production
my-app:isPrimaryRegion: "false"
Stack Policies
Protect Resources
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
tags: {
Name: `db-${environment}`,
},
}, {
protect: environment === "production",
});
const bucket = new aws.s3.Bucket("data", {
tags: {
Name: `data-${environment}`,
},
}, {
protect: environment === "production",
});
Retain Resources
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const environment = config.require("environment");
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
finalSnapshotIdentifier: environment === "production"
? `final-snapshot-${Date.now()}`
: undefined,
skipFinalSnapshot: environment !== "production",
}, {
retainOnDelete: environment === "production",
});
Stack Secrets Management
Using Encrypted Secrets
pulumi config set --secret dbPassword mySecurePassword123
pulumi config set --secret apiKey sk_live_abc123xyz789
pulumi config
pulumi config get dbPassword --show-secrets
Secrets in Code
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const config = new pulumi.Config();
const dbPassword = config.requireSecret("dbPassword");
const apiKey = config.requireSecret("apiKey");
const db = new aws.rds.Instance("database", {
engine: "postgres",
instanceClass: "db.t3.micro",
allocatedStorage: 20,
username: "admin",
password: dbPassword,
});
const dbPasswordParam = new aws.ssm.Parameter("db-password", {
name: "/app/database/password",
type: "SecureString",
value: dbPassword,
});
const apiKeyParam = new aws.ssm.Parameter("api-key", {
name: "/app/api/key",
: ,
: apiKey,
});
connectionString = pulumi.(
pulumi.
);
Stack Refresh and State
Refresh Stack State
pulumi refresh
pulumi refresh --yes
pulumi refresh --target urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::my-bucket
pulumi refresh --diff
Stack State Management
pulumi stack --show-urns
pulumi stack --show-urns | grep my-bucket
pulumi state delete 'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::my-bucket'
pulumi state rename 'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::old-name' \
'urn:pulumi:dev::myapp::aws:s3/bucket:Bucket::new-name'
When to Use This Skill
Use the pulumi-stacks skill when you need to:
- Deploy infrastructure to multiple environments (dev, staging, prod)
- Manage environment-specific configurations
- Create isolated instances of the same infrastructure
- Share infrastructure outputs between projects
- Implement multi-region deployments
- Separate infrastructure concerns (networking, databases, applications)
- Manage secrets per environment
- Track infrastructure state per environment
- Implement progressive deployment strategies
- Organize complex infrastructure into manageable units
- Apply environment-specific policies and protections
- Maintain consistent infrastructure across environments
Best Practices
- Naming Convention: Use consistent stack naming like
<env> or <region>-<env> (e.g., prod, us-east-1-prod)
- Configuration Files: Keep stack config files in version control (except secrets)
- Environment Isolation: Never share state between environments; each environment gets its own stack
- Stack References: Use stack references instead of duplicating infrastructure code
- Secrets Management: Always use
--secret flag for sensitive values
- Progressive Deployment: Deploy to dev first, then staging, finally production
- State Backups: Regularly export stack state for disaster recovery
- Resource Protection: Enable
protect option for critical production resources
- Tagging Strategy: Apply consistent tags across all environments for cost tracking
- Stack Outputs: Export all values needed by other stacks or external systems
- Configuration Validation: Validate configuration values before creating resources
- Environment Parity: Keep environments as similar as possible, differing only in scale
- Automation: Use CI/CD pipelines for stack deployments
- Documentation: Document stack dependencies and required configuration
- State Encryption: Use encrypted state backends for sensitive infrastructure
Common Pitfalls
- Hardcoded Values: Hardcoding environment-specific values instead of using configuration
- Shared State: Attempting to share stack state between environments
- Missing Config: Deploying to new stack without setting required configuration
- Unencrypted Secrets: Storing secrets as plain text in configuration
- Inconsistent Naming: Using different naming conventions across stacks
- Broken References: Stack references that point to non-existent stacks or outputs
- Missing Exports: Not exporting values needed by dependent stacks
- Config Drift: Manual changes to config files not reflected in version control
- No Resource Protection: Forgetting to protect critical production resources
- Stack Sprawl: Creating too many stacks without clear organization
- Missing Validation: Not validating configuration before deployment
- Circular Dependencies: Creating circular stack references
- No Backup Strategy: Not exporting stack state for disaster recovery
- Environment Differences: Production significantly different from other environments
- Poor Secret Management: Checking encrypted secrets into public repositories without proper key management
Resources