Any real-world project has at least two environments — dev and prod — and often more: qa, staging, sometimes even region-specific environments.
Why? Because you want to catch mistakes before they reach production and impact your users.
From an infrastructure perspective, environments often differ significantly: different data, sizes, pricing tiers… even access controls. It’s not just about testing functionality — it’s about protecting production, reducing costs, and keeping things clean.
📚 Pulumi Stacks 101
In Pulumi, environments are managed through stacks. A stack is an isolated instance of your Pulumi program, with its own configuration, state, and resources.
When you initialized your project, you may have already created a stack — but you can add more at any time. For example:
pulumi stack init dev
pulumi stack init prod
This sets up two separate environments. To switch between them:
pulumi stack select dev
Commands like pulumi up
, preview
, and refresh
will apply to the currently selected stack.
To list all your stacks and see which one is active:
pulumi stack ls
🛠 Using Configuration Per Environment
Pulumi uses per-stack config files like Pulumi.dev.yaml
and Pulumi.prod.yaml
to separate settings between environments—keeping your code the same while using different values.
You can define environment-specific values for things like resource names, SKUs, or feature toggles. For example:
pulumi config set storageAccessTier Cool --stack dev
pulumi config set storageAccessTier Hot --stack prod
If you’re working within the currently selected stack, you can omit the --stack
parameter:
pulumi config set storageAccessTier Cool
To read configuration values in your code:
import * as pulumi from "@pulumi/pulumi";
const config = new pulumi.Config();
const storageAccessTier = config.require("storageAccessTier");
✨ Small Demo: Different Storage Accounts for Dev and Prod
Let’s create two storage accounts — one for dev
and one for prod
—with different tiers and naming based on the environment.
First, switch to the dev environment and set up your config:
pulumi stack select dev
pulumi config set env dev
pulumi config set storageAccountTier Standard_LRS
Then, do the same for the prod environment:
pulumi stack select prod
pulumi config set env dev
pulumi config set storageAccountTier Standard_GRS
We’ve now defined two config values per stack: one for the environment name and one for the storage account tier.
While you could use the stack name (dev
, prod
, etc.) as the environment identifier, it’s often better to separate those concepts. For example, your environment might be named qa-west-europe
, while your stack name is just qawe
.
Now let’s create a storage account and a resource group per environment using those configs:
import * as pulumi from "@pulumi/pulumi";
import * as resources from "@pulumi/azure-native/resources";
import * as storage from "@pulumi/azure-native/storage";
const config = new pulumi.Config();
const environment = config.require("env");
const storageAccountTier = config.require("storageAccountTier");
// Create an Azure Resource Group
const resourceGroup = new resources.ResourceGroup("resourceGroup", {
resourceGroupName: `rg-${environment}`,
});
// Create an Azure resource (Storage Account)
const storageAccount = new storage.StorageAccount("sa", {
accountName: `sapulumitest${environment}`,
resourceGroupName: resourceGroup.name,
sku: {
name: storageAccountTier,
},
kind: storage.Kind.StorageV2,
});
This setup creates resource groups named rg-dev
and rg-prod
, along with storage accounts like sapulumitestdev
and sapulumitestprod
, each using its specified storage tier.
✅ Easy peasy.
🧠 Pro Tips
- Keep stack names simple and lowercase:
dev
,prod
,test
. - You can export outputs per stack (like connection strings) and fetch them easily.
- Don’t hardcode anything per environment inside the code — always use config where possible.
✅ Wrap-Up
Pulumi stacks give you a clean, structured way to manage different environments like dev, staging, and prod — each with its own state and settings. By combining stacks with environment-specific configuration, you keep your infrastructure flexible, consistent, and easy to maintain.
In the next article, we’ll dive deeper into Pulumi configuration: how to manage complex values, structure settings, and securely handle sensitive data using secrets.
👉 Code for this post is available here on GitHub
Top comments (0)