Managing Multi-Environment Infrastructure with Pulumi Stacks
Stop duplicating infrastructure code for dev and prod. Learn how to use Pulumi Stacks to isolate environments, manage per-stack configurations, and secure secrets.
22 Sept 2026, 06:51 UTC

The 'Copy-Paste' Infrastructure Trap
A common friction point in infrastructure as code (IaC) is the temptation to create separate directories or files for different environments—dev.ts, staging.ts, and prod.ts. This approach leads to configuration drift, where a critical security group update is applied to development but forgotten in production, or where a naming convention diverges across environments, making monitoring scripts fail.
The solution is to decouple the infrastructure logic (the "what") from the environment configuration (the "where" and "how much"). In Pulumi, this is handled through Stacks.
What is a Pulumi Stack?
A Stack is an isolated instance of your Pulumi program. Think of your code as a class and a Stack as an object instantiated from that class. You write the code once, and then you deploy it multiple times into different Stacks (e.g., dev, staging, prod).
Each stack maintains its own independent state file. This means that when you run a deployment against the dev stack, Pulumi has no visibility into or impact on the resources managed by the prod stack. This isolation is the primary guardrail preventing accidental production outages during routine development tests.
Handling Environment-Specific Variables
To make a single program work across different environments, you need a way to inject variables. Pulumi uses a configuration system that allows you to define values per stack. These are stored in Pulumi..yaml files.
Common candidates for stack configuration include:
- Instance Sizes: Using
t3.microfor dev andm5.largefor prod. - Region: Deploying to
us-east-1for staging andeu-central-1for production to meet residency requirements. - Scaling Limits: Setting a maximum of 2 nodes in dev but 20 in prod.
Worked Example: Dynamic Instance Sizing
Assume you are using TypeScript and want to vary your VM size based on the environment. First, initialize your stacks and set the configuration via the CLI:
# Run these in your project root
pulumi stack init dev
pulumi config set instanceSize t3.micro
pulumi stack init prod
pulumi config set instanceSize m5.large
Now, reference these values in your code using the pulumi.Config class:
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Initialize the config object
const config = new pulumi.Config();
// Retrieve the value for the current active stack
const instanceSize = config.require("instanceSize");
const server = new aws.ec2.Instance("web-server", {
instanceType: instanceSize,
ami: "ami-0c55b159cbfafe1f0", // Example AMI
});
Verification: To check which configuration is active, run pulumi config. When you run pulumi up, the preview will show the specific instance type associated with the currently selected stack.
Managing Secrets Securely
Hardcoding API keys or database passwords in YAML files is a security risk. Pulumi Stacks support native secret encryption. When you use the --secret flag, Pulumi encrypts the value using a provider-specific key (or the Pulumi Service backend) before writing it to the YAML file.
# Run as a user with project permissions
pulumi config set --secret dbPassword "super-secret-password"
In the resulting YAML file, the value will appear as a ciphertext string. In your code, Pulumi treats these as Output<string> types, ensuring they are masked in the CLI output during deployments.
Trade-offs and Limitations
While Stacks provide powerful isolation, they introduce the risk of configuration drift. If a team member manually changes a setting in the AWS or Azure Console for the prod stack without updating the Pulumi config, the state file becomes inaccurate. To mitigate this, avoid manual console changes and treat the Pulumi YAML files as the single source of truth.
Additionally, in team environments, state locking is critical. If two engineers run pulumi up on the same stack simultaneously, the state file could be corrupted. If you are using a self-managed backend (like S3), you must implement your own locking mechanism (e.g., via DynamoDB); the Pulumi Service handles this automatically.
Actionable Summary
To implement a professional multi-environment workflow:
- Create distinct stacks for every environment using
pulumi stack init [name]. - Move all environment-specific strings and numbers into
pulumi config. - Use
--secretfor any sensitive data to ensure encryption at rest. - Verify the active stack with
pulumi stack lsbefore executingpulumi up.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.