Pulumi - Street-Level Ops¶
Quick Diagnosis Commands¶
# Check Pulumi CLI version
pulumi version
# Login to Pulumi state backend
pulumi login # Pulumi Cloud (default)
pulumi login s3://mybucket # S3 backend
pulumi login file://~/.pulumi # local backend
# Preview changes (like terraform plan)
pulumi preview
# Apply changes
pulumi up
# Apply without confirmation prompt (CI use)
pulumi up --yes
# Destroy all resources
pulumi destroy
# Show current stack outputs
pulumi stack output
# Show specific output
pulumi stack output vpcId
# Show resource state
pulumi stack
# Stack management
pulumi stack init prod # create new stack
pulumi stack select prod # switch to stack
pulumi stack ls # list all stacks
pulumi stack rm dev # delete stack (after destroy)
# Config management
pulumi config set aws:region us-east-1
pulumi config set --secret dbPassword hunter2
pulumi config get dbPassword
pulumi config ls # show all config for current stack
pulumi config rm dbPassword # remove a config key
# Refresh state against real infra (like terraform refresh)
pulumi refresh
# Detect drift
pulumi preview --refresh # refresh then show diff
# Import existing resource into Pulumi state
pulumi import aws:ec2/instance:Instance myInstance i-0123456789abcdef0
# State operations
pulumi state export > backup.json # export state
pulumi state import < backup.json # import state
pulumi state unprotect <urn> # remove deletion protection
pulumi state delete <urn> # remove from state (not from cloud)
# Rename a resource in state without recreating
pulumi state rename <old-urn> <new-name>
# Run in watch mode (auto-deploy on code changes)
pulumi watch
# Run the Pulumi automation API from a script
pulumi up --cwd ./my-stack-dir --yes
Common Scenarios¶
Scenario 1: Preview Shows Unexpected Resource Replacement¶
Pulumi wants to delete and recreate a resource you expected to update in-place.
# Run preview to see what would change and why
pulumi preview --diff
# Look for "replace" in the output
# Example: aws:ec2/instance:Instance (replace)
# ~ instanceType: "t3.micro" => "t3.small"
# +-tags: ...
# Some properties force replacement (immutable in the cloud provider)
# Check provider docs for which properties trigger replacement vs update
# To avoid accidental replacement, protect critical resources
# In Python:
# instance = aws.ec2.Instance("web", ..., opts=pulumi.ResourceOptions(protect=True))
# Check what's protected in state
pulumi stack export | jq '.deployment.resources[] | select(.protect == true) | .urn'
# To import current state and avoid replacement, re-import and realign config
pulumi import aws:ec2/instance:Instance myInstance i-0existingid
Scenario 2: Stack in Failed State After Interrupted Update¶
A pulumi up was interrupted mid-apply. The stack shows pending operations.
# Check stack status
pulumi stack
# See pending operations
pulumi stack export | jq '.deployment.pending_operations'
# If resources are in a bad state, export and fix state
pulumi stack export > state.json
# Edit state.json to remove or fix pending_operations array
pulumi stack import < state.json
# Cancel a pending operation (Pulumi Cloud)
pulumi cancel
# Alternatively, do a targeted refresh on affected resources
pulumi refresh --target <urn>
# Force a clean preview after fixing state
pulumi preview --refresh
Scenario 3: Secret Config Values Leaking Into Output¶
A stack output shows a database password in plaintext.
# Mark output as secret so it's encrypted at rest and redacted in display
# In Python:
# pulumi.export("dbPassword", pulumi.Output.secret(db.password))
# Check current outputs (secrets shown as [secret])
pulumi stack output
# Access secret output value (requires explicit flag)
pulumi stack output --show-secrets dbPassword
# Rotate a secret config value
pulumi config set --secret dbPassword new_password_here
pulumi up # resources using this config will see the new value
# Check which config values are secret
pulumi config ls
# Secret values show as [secret] in the Type column
Scenario 4: Cross-Stack References¶
One stack needs outputs from another (e.g., VPC ID from network stack consumed by app stack).
# In the network stack — export the VPC ID
# network/__main__.py
import pulumi
vpc = aws.ec2.Vpc("main", cidr_block="10.0.0.0/16")
pulumi.export("vpc_id", vpc.id)
pulumi.export("private_subnets", private_subnets.apply(lambda s: [x.id for x in s]))
# In the app stack — reference network stack outputs
# app/__main__.py
import pulumi
from pulumi import StackReference
# Reference another stack
network = StackReference("myorg/network/prod")
vpc_id = network.get_output("vpc_id")
# Use the reference
instance = aws.ec2.Instance("web",
subnet_id=vpc_id,
...
)
# List stack references from a stack
pulumi stack export | jq '.deployment.resources[] | select(.type == "pulumi:pulumi:StackReference") | .inputs'
# Update stack references (after network stack changes)
cd app-stack && pulumi up
Key Patterns¶
Python Stack Structure¶
# __main__.py
import pulumi
import pulumi_aws as aws
# Config
config = pulumi.Config()
env = config.require("environment")
db_password = config.require_secret("dbPassword")
# Resources
vpc = aws.ec2.Vpc("main",
cidr_block="10.0.0.0/16",
tags={"Environment": env},
)
subnet = aws.ec2.Subnet("public",
vpc_id=vpc.id,
cidr_block="10.0.1.0/24",
availability_zone="us-east-1a",
)
# Outputs
pulumi.export("vpc_id", vpc.id)
pulumi.export("subnet_id", subnet.id)
TypeScript Stack Structure¶
// index.ts
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",
tags: { Environment: environment },
});
export const vpcId = vpc.id;
Provider Version Pinning¶
# Pulumi.yaml
name: my-stack
runtime: python
description: My infrastructure stack
plugins:
providers:
- name: aws
version: 6.x.x # pin major version
# requirements.txt
pulumi>=3.0.0,<4.0.0
pulumi-aws>=6.0.0,<7.0.0
Automation API for Programmatic Deployments¶
# deploy.py — run Pulumi from Python code
import pulumi
from pulumi.automation import LocalWorkspace, Stack, UpOptions
async def deploy():
stack = await LocalWorkspace.create_or_select_stack_async(
stack_name="prod",
work_dir="/path/to/stack",
)
# Set config programmatically
await stack.set_config_async("aws:region", ConfigValue(value="us-east-1"))
await stack.set_config_async("dbPassword", ConfigValue(value=secret_value, secret=True))
# Preview and up
preview = await stack.preview_async()
print(preview.stdout)
result = await stack.up_async(on_output=print, opts=UpOptions(parallel=10))
print(f"Update successful: {result.summary.result}")
Drift Detection in CI¶
# In CI: detect drift without applying
pulumi preview --refresh --diff --expect-no-changes
# Exit code 1 if drift detected — fails the CI job
# More targeted: only fail on resource changes, not output-only changes
pulumi preview --refresh --diff --expect-no-changes 2>&1 | \
grep -q "^Resources:" || exit 1
Managing Multiple Environments¶
# Pulumi stacks map 1:1 to environments
pulumi stack init dev
pulumi stack init staging
pulumi stack init prod
# Per-stack config
pulumi stack select dev
pulumi config set instanceType t3.micro
pulumi stack select prod
pulumi config set instanceType m5.xlarge
# Apply to specific environment
pulumi stack select prod && pulumi up --yes