2.6. Stacks

Stacks are Pulumi’s mechanism for running the same program against isolated environments — dev, staging, and prod each get their own state, their own config, and their own deployed resources.

Step 1 — Inspect the current stack

Check which stack is currently active and list all stacks in the project:

pulumi stack
pulumi stack ls

Explanation

Every Pulumi operation targets exactly one stack. pulumi stack prints the active stack and its last-known resource count. pulumi stack ls shows all stacks that exist in the current backend for this project.

At this point you should see a single stack — dev — marked as current with an asterisk. The dev container is still running on port 8080 — leave it up.

Each stack can bind a different port

Because the port is a config value, dev uses 8080 and prod will use 8081 — both can run simultaneously on the same Docker host. In real cloud environments the isolation is even stronger: each stack provisions resources in its own resource group, so stacks never compete for the same network port at all.


Step 2 — Create a prod stack

Create a new isolated stack named prod:

pulumi stack init prod
pulumi stack ls

Explanation

pulumi stack init creates a new stack entry in the state backend. The new stack is completely empty — no resources, no config, no history from dev. The stacks share the same Python program but maintain separate state files.

After init, prod becomes the active stack automatically. You should see both stacks in pulumi stack ls, with prod now marked as current.

A new config file appears: config/Pulumi.prod.yaml

pulumi stack init creates config/Pulumi.prod.yaml in your project. This file stores all config values you set with pulumi config set while prod is active — image tags, region, any stack-specific settings. Commit this file so the config travels with the code and other team members get the same values when they select prod.

Config is per-stack

Stack config values set with pulumi config set apply only to the active stack. Values read with config.require() or config.require_secret() must be set on every stack or have a default value in Pulumi.yaml — Pulumi fails loudly if they are missing. Values read with config.get() or config.get_object() are optional and fall back to their defaults.


Step 3 — Deploy to prod

Config is per-stack — prod starts with no values. Set the required config before deploying:

pulumi config set image-tag 0.4-plain-text
pulumi config set port 8081
pulumi config set --secret api-key prod-fake-key

Run a preview and deploy:

pulumi preview
pulumi up

Verify the resources and outputs are present in prod:

pulumi stack output
curl http://localhost:8081

Confirm that the dev container is still running on port 8080:

docker ps --format 'table {{.Names}}\t{{.Ports}}'

Explanation

pulumi up targets whichever stack is currently active — prod in this case. Pulumi reads the empty prod state, sees that none of the program’s resources exist yet, and creates them from scratch.

With port set to 8081 for prod and 8080 for dev, both containers run simultaneously on the same Docker host. The dev stack is completely unaffected — its state file, container, and outputs remain exactly as they were. This isolation is the core guarantee stacks provide: a deploy on one stack cannot modify resources owned by another stack.


Step 4 — Switch back to dev

Select the dev stack and confirm its outputs are intact:

pulumi stack select dev
pulumi stack output
pulumi stack ls

Explanation

pulumi stack select changes the active stack without touching any resources. All subsequent CLI commands (pulumi up, pulumi preview, pulumi stack output, …) target the newly selected stack.

This is how teams manage multiple environments with a single codebase:

StackPurpose
devIndividual or shared development; throwaway deploys
stagingPre-production; mirrors prod config and scale
prodLive environment; protected resources
One stack per environment, one environment per stack

Avoid using feature branches to vary infrastructure. Use stacks instead — each stack carries its own config and state, so a dev stack can use a cheaper VM size while prod uses the production tier, all in the same program.


Step 5 — Destroy the prod stack

Tear down the resources in prod and remove the stack to keep the project tidy:

pulumi stack select prod
pulumi destroy
pulumi stack rm prod

Confirm the stack is gone:

pulumi stack ls

Explanation

pulumi destroy removes all resources owned by the active stack and clears them from state, but leaves the empty stack record in the backend. pulumi stack rm deletes that record as well. After this sequence only dev remains.

In production workflows, stacks like prod are never removed — they persist for the lifetime of the environment. stack rm is mainly useful for cleaning up short-lived feature or test stacks.