DEV Community

Cover image for Pulumi + Azure: Managing Multiple Environments (Dev, Staging, Prod)
Nedim Hozic
Nedim Hozic

Posted on

Pulumi + Azure: Managing Multiple Environments (Dev, Staging, Prod)

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
Enter fullscreen mode Exit fullscreen mode

This sets up two separate environments. To switch between them:

pulumi stack select dev
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

🛠 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
Enter fullscreen mode Exit fullscreen mode

If you’re working within the currently selected stack, you can omit the --stack parameter:

pulumi config set storageAccessTier Cool
Enter fullscreen mode Exit fullscreen mode

To read configuration values in your code:

import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();
const storageAccessTier = config.require("storageAccessTier");
Enter fullscreen mode Exit fullscreen mode

✨ 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
Enter fullscreen mode Exit fullscreen mode

Then, do the same for the prod environment:

pulumi stack select prod

pulumi config set env dev
pulumi config set storageAccountTier Standard_GRS
Enter fullscreen mode Exit fullscreen mode

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,
});
Enter fullscreen mode Exit fullscreen mode

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)