Skip to content

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