Pulumi Commands Reference
Your complete guide to managing infrastructure with project-planton pulumi commands.
Overview
Think of Pulumi as your infrastructure's version control system. Just as Git lets you commit, preview diffs, and push code changes, Pulumi's lifecycle commands let you initialize, preview, deploy, refresh, and destroy infrastructure. The project-planton CLI wraps these Pulumi operations with manifest-driven workflows, giving you a consistent experience across all cloud providers.
The Infrastructure Lifecycle
┌──────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐
│ init │ -> │ preview │ -> │ up │ -> │ refresh │ -> │ destroy │ -> │ delete │
└──────────┘ └─────────┘ └────────┘ └─────────┘ └─────────┘ └────────┘
│ │ │ │ │ │
Create Review Deploy Sync State Teardown Remove
Stack Changes Resources with Cloud Resources Stack
Key Concepts
Manifest: A YAML file describing your infrastructure resource (e.g., r2-bucket.yaml, eks-cluster.yaml). Think of it as a blueprint.
Stack: A deployment instance with its own state. The stack name follows the format <org>/<project>/<stack> (e.g., planton-cloud/planton-cloud/prod.CloudflareR2Bucket.my-bucket). Your manifest contains this in its labels.
Module Directory: Where the Pulumi IaC code lives. Usually auto-detected from your manifest's resource kind, but can be overridden with --module-dir.
State Backend: Where Pulumi stores your infrastructure's state. Configure this once with pulumi login (supports Pulumi Cloud, S3, GCS, or local file system).
Commands
init - Initialize a New Stack
What it does: Creates a new Pulumi stack in your backend for the resource defined in your manifest. Think of this as "initializing a Git repository" for your infrastructure.
When to use:
- First time deploying a new resource
- Creating a new environment (dev/staging/prod) for an existing resource type
- After getting "stack not found" errors from other commands
Behavior:
- Reads the stack FQDN from your manifest's
pulumi.project-planton.org/stack.namelabel - Creates the stack in your configured Pulumi backend
- If the stack already exists, gracefully skips initialization (idempotent operation)
- Does NOT create any cloud resources—it only prepares the state tracking
Usage:
project-planton pulumi init --manifest <manifest-file> [flags]
Examples:
# Initialize a Cloudflare R2 bucket stack
project-planton pulumi init \
--manifest ops/cloud-resources/prod/r2-bucket.yaml
# Initialize using kustomize overlay (for projects using kustomize)
project-planton pulumi init \
--kustomize-dir backend/services/api \
--overlay prod
# Initialize with explicit module directory (for development/testing)
project-planton pulumi init \
--manifest ops/resources/vpc.yaml \
--module-dir ~/projects/custom-modules/aws-vpc
What you'll see:
● Loading manifest...
✔ Manifest loaded
● Validating manifest...
✔ Manifest validated
● Initializing Pulumi stack...
🤝 Handing off to Pulumi...
Output below is from Pulumi
Using Pulumi stack from manifest labels: planton-cloud/planton-cloud/prod.CloudflareR2Bucket.pipeline-logs
pulumi module directory: /path/to/module
Initializing stack: planton-cloud/planton-cloud/prod.CloudflareR2Bucket.pipeline-logs
Created stack 'planton-cloud/planton-cloud/prod.CloudflareR2Bucket.pipeline-logs'
✓ Successfully initialized stack: planton-cloud/planton-cloud/prod.CloudflareR2Bucket.pipeline-logs
✔ Pulumi execution succeeded
preview - Preview Infrastructure Changes
What it does: Shows you what changes Pulumi will make to your infrastructure without actually applying them. This is like git diff before committing—you see what's going to change before it happens.
When to use:
- Before running
upto understand what will be created/modified/deleted - To validate your manifest changes produce the expected infrastructure changes
- During code review to demonstrate infrastructure changes
- When debugging unexpected behavior
Behavior:
- Compares your manifest against the current infrastructure state
- Shows a detailed diff: additions (+), modifications (~), deletions (-)
- Does NOT modify any cloud resources
- Does NOT modify Pulumi state
- Requires the stack to exist (run
initfirst if needed)
Usage:
project-planton pulumi preview --manifest <manifest-file> [flags]
Examples:
# Preview changes for a Kubernetes deployment
project-planton pulumi preview \
--manifest services/api/deployment.yaml
# Preview with field overrides (useful for testing different configurations)
project-planton pulumi preview \
--manifest services/api/deployment.yaml \
--set spec.replicas=5 \
--set spec.container.image.tag=v2.0.0
# Preview using kustomize (common for multi-environment setups)
project-planton pulumi preview \
--kustomize-dir backend/services/api \
--overlay staging
Reading the output:
Previewing update (planton-cloud/prod.CloudflareR2Bucket.logs):
Type Name Plan Info
+ pulumi:pulumi:Stack planton-cloud create
+ └─ cloudflare:R2Bucket bucket create
Resources:
+ 2 to create
Legend:
+= Resource will be created~= Resource will be modified (shows detailed diff of what changes)-= Resource will be deleted+-= Resource will be replaced (delete + create, often due to immutable property changes)
up (or update) - Deploy Infrastructure
What it does: Applies your manifest to create, update, or configure cloud resources. This is the "make it so" command—it actually executes the infrastructure changes.
When to use:
- After reviewing changes with
previewand confirming they look correct - Initial deployment of new infrastructure
- Updating existing infrastructure with new configurations
- Applying configuration changes (scaling, updates, feature flags)
Behavior:
- Creates the stack if it doesn't exist (no need to run
initseparately) - Shows a preview of changes (unless you use
--yesflag) - Waits for your confirmation before proceeding (unless
--yesis provided) - Creates/updates/deletes cloud resources to match your manifest
- Updates Pulumi state to reflect the new infrastructure state
- Rolls back automatically if deployment fails (where provider supports it)
Usage:
project-planton pulumi up --manifest <manifest-file> [flags]
Aliases: You can use update or up interchangeably.
Examples:
# Interactive deployment (will show preview and ask for confirmation)
project-planton pulumi up \
--manifest ops/resources/database.yaml
# Non-interactive deployment (CI/CD pipelines)
project-planton pulumi up \
--manifest ops/resources/database.yaml \
--yes
# Deploy with field overrides
project-planton pulumi up \
--manifest ops/resources/cache.yaml \
--set spec.instanceSize=large \
--set spec.replicas=3
# Deploy using kustomize overlay
project-planton pulumi up \
--kustomize-dir backend/services/worker \
--overlay prod \
--yes
What you'll see:
● Loading manifest...
✔ Manifest loaded
● Validating manifest...
✔ Manifest validated
● Preparing Pulumi execution...
✔ Execution prepared
🤝 Handing off to Pulumi...
Output below is from Pulumi
Using Pulumi stack from manifest labels: planton-cloud/planton-cloud/prod.GcpCloudSql.main-db
Previewing update (planton-cloud/planton-cloud/prod.GcpCloudSql.main-db):
Type Name Plan
+ pulumi:pulumi:Stack planton-cloud create
+ ├─ gcp:sql:DatabaseInstance main-db create
+ ├─ gcp:sql:Database app-db create
+ └─ gcp:sql:User app-user create
Resources:
+ 4 to create
Do you want to perform this update? yes
Updating (planton-cloud/planton-cloud/prod.GcpCloudSql.main-db):
Type Name Status
+ pulumi:pulumi:Stack planton-cloud created (3s)
+ ├─ gcp:sql:DatabaseInstance main-db created (185s)
+ ├─ gcp:sql:Database app-db created (8s)
+ └─ gcp:sql:User app-user created (5s)
Outputs:
connection_name: "my-project:us-central1:main-db-xyz123"
database_name: "app-db"
Resources:
+ 4 created
Duration: 3m21s
✔ Pulumi execution succeeded
Important Notes:
- Preview-then-apply workflow: By default,
upshows you a preview and waits for confirmation. This is your safety net. - Automatic stack creation: Unlike
preview,upwill create the stack if it doesn't exist, so you don't always need to runinitfirst. - State locking: Pulumi automatically locks state during updates to prevent concurrent modifications.
- Failed updates: If an update fails midway, Pulumi state will reflect the partial changes. You can re-run
upto continue or userefreshto sync state.
refresh - Sync State with Reality
What it does: Compares Pulumi's state file against the actual resources in your cloud provider and updates the state to match reality. Think of this as "git fetch" for infrastructure—it brings your local understanding up to date with what's actually deployed.
When to use:
- After manual changes made outside Pulumi (e.g., via cloud console, CLI, or other tools)
- Before running
upordestroyto ensure state accuracy - After failed deployments to resynchronize state
- When troubleshooting drift between desired and actual state
- After importing existing resources into Pulumi management
Behavior:
- Queries your cloud provider for the current state of managed resources
- Updates Pulumi state to reflect actual resource properties
- Does NOT modify any cloud resources
- Does NOT change your manifest file
- Shows what changed in the state (if anything)
- Detects resources that were deleted outside Pulumi
Usage:
project-planton pulumi refresh --manifest <manifest-file> [flags]
Examples:
# Refresh to sync state after manual changes
project-planton pulumi refresh \
--manifest ops/resources/s3-bucket.yaml
# Non-interactive refresh (for automation)
project-planton pulumi refresh \
--manifest ops/resources/s3-bucket.yaml \
--yes
# Refresh before important operations
project-planton pulumi refresh \
--manifest ops/resources/production-db.yaml \
--yes && \
project-planton pulumi up \
--manifest ops/resources/production-db.yaml
What you'll see:
● Loading manifest...
✔ Manifest loaded
● Validating manifest...
✔ Manifest validated
● Preparing Pulumi execution...
✔ Execution prepared
🤝 Handing off to Pulumi...
Output below is from Pulumi
Refreshing (planton-cloud/planton-cloud/prod.AwsS3Bucket.assets):
Type Name Status Info
pulumi:pulumi:Stack planton-cloud
~ └─ aws:s3:Bucket assets updated [diff: ~tags]
Outputs:
bucket_name: "assets-prod-xyz123"
Resources:
~ 1 updated
1 unchanged
Duration: 5s
✔ Pulumi execution succeeded
Understanding Drift:
Drift occurs when someone (or something) modifies infrastructure outside of Pulumi:
Before refresh:
Pulumi State: bucket versioning = false
Actual Cloud:
AWS Console: someone enabled versioning = true
After refresh:
Pulumi State: bucket versioning = true (synced!)
Next steps after refresh:
- If changes match your manifest → You're good, carry on
- If unexpected changes → Investigate who/what made them
- If you want to revert manual changes → Update manifest if needed, then run
up
destroy - Teardown Infrastructure
What it does: Deletes all cloud resources managed by the Pulumi stack. This is the "rm -rf" of infrastructure—use with caution. The stack itself remains (with empty state) unless you manually delete it.
When to use:
- Tearing down temporary environments (dev, testing, ephemeral previews)
- Decommissioning infrastructure that's no longer needed
- Cleaning up after testing or experimentation
- Cost optimization (shutting down unused resources)
- Before major refactoring (destroy old, deploy new)
Behavior:
- Shows a preview of resources to be deleted
- Waits for explicit confirmation (unless
--yesis provided) - Deletes resources in reverse dependency order (children before parents)
- Updates Pulumi state to reflect deletion
- Leaves the stack itself intact (but with no resources)
- Cannot be undone once confirmed
Usage:
project-planton pulumi destroy --manifest <manifest-file> [flags]
Examples:
# Interactive destroy (will ask for confirmation)
project-planton pulumi destroy \
--manifest ops/resources/dev-cluster.yaml
# Non-interactive destroy (automation/CI)
project-planton pulumi destroy \
--manifest ops/resources/test-environment.yaml \
--yes
# Destroy temporary environment
project-planton pulumi destroy \
--kustomize-dir backend/services/api \
--overlay pr-123 \
--yes
What you'll see:
● Loading manifest...
✔ Manifest loaded
● Validating manifest...
✔ Manifest validated
● Preparing Pulumi execution...
✔ Execution prepared
🤝 Handing off to Pulumi...
Output below is from Pulumi
Previewing destroy (planton-cloud/planton-cloud/dev.GkeCluster.test-cluster):
Type Name Plan
- pulumi:pulumi:Stack planton-cloud delete
- ├─ gcp:container:Cluster test-cluster delete
- └─ gcp:container:NodePool default-pool delete
Resources:
- 3 to delete
Do you want to perform this destroy? yes
Destroying (planton-cloud/planton-cloud/dev.GkeCluster.test-cluster):
Type Name Status
- pulumi:pulumi:Stack planton-cloud deleted
- ├─ gcp:container:NodePool default-pool deleted (90s)
- └─ gcp:container:Cluster test-cluster deleted (180s)
Resources:
- 3 deleted
Duration: 4m35s
✔ Pulumi execution succeeded
⚠️ Safety Warnings:
- Permanent deletion: Most cloud providers permanently delete resources. Some have soft-delete/trash, but don't count on it.
- Data loss: Databases, storage buckets, and other stateful resources will lose their data unless you have backups.
- Dependency risk: If other resources depend on what you're destroying, they may break.
- No undo: Once you confirm, there's no rollback. The resources are gone.
Best Practices:
# ✅ Good: Review before destroying
project-planton pulumi preview --manifest prod.yaml # See what exists
project-planton pulumi destroy --manifest prod.yaml # Interactive confirmation
# ⚠️ Risky: Blind destruction
project-planton pulumi destroy --manifest prod.yaml --yes
# ✅ Good: Backup data first
aws s3 sync s3://my-bucket ./backup-$(date +%Y%m%d)/
project-planton pulumi destroy --manifest s3-bucket.yaml
# ✅ Good: Verify manifest before destroying
cat prod.yaml # Make absolutely sure this is the right file
project-planton pulumi destroy --manifest prod.yaml
delete (or rm) - Remove Stack Metadata
What it does: Deletes a Pulumi stack and all its configuration/state from the backend. This removes the stack metadata itself, not the cloud resources. Think of this as "deleting the Git repository" for your infrastructure tracking.
When to use:
- After destroying all resources and you no longer need the stack tracking
- Cleaning up stack metadata for decommissioned projects
- Removing accidentally created or test stacks
- Freeing up stack names for reuse
Behavior:
- Removes the stack from your Pulumi backend (state storage)
- Deletes all stack configuration and history
- Does NOT destroy cloud resources (run
destroyfirst if resources exist) - By default, refuses to delete stacks that still have resources
- With
--force, removes stack even if resources exist (dangerous!) - Cannot be undone once executed
Usage:
project-planton pulumi delete --manifest <manifest-file> [flags]
Aliases: You can use delete or rm interchangeably.
Examples:
# Standard workflow: destroy resources first, then remove stack
project-planton pulumi destroy \
--manifest ops/resources/temp-env.yaml \
--yes
# After resources are gone, remove the stack metadata
project-planton pulumi delete \
--manifest ops/resources/temp-env.yaml
# Using the 'rm' alias
project-planton pulumi rm \
--manifest ops/resources/old-stack.yaml
# Force removal (skip resource check) - use with extreme caution
project-planton pulumi delete \
--manifest ops/resources/abandoned-stack.yaml \
--force
# Remove stack via explicit stack FQDN
project-planton pulumi delete \
--manifest ops/resources/resource.yaml \
--stack my-org/my-project/dev.TestStack.old
What you'll see:
● Loading manifest...
✔ Manifest loaded
● Validating manifest...
✔ Manifest validated
● Deleting Pulumi stack...
🤝 Handing off to Pulumi...
Output below is from Pulumi
Using Pulumi stack from manifest labels: planton-cloud/planton-cloud/dev.TestResource.temp
pulumi module directory: /path/to/module
Removing stack: planton-cloud/planton-cloud/dev.TestResource.temp
Stack 'planton-cloud/planton-cloud/dev.TestResource.temp' has been removed!
✓ Successfully removed stack: planton-cloud/planton-cloud/dev.TestResource.temp
✔ Pulumi execution succeeded
⚠️ Critical Warnings:
- Resources check: By default, Pulumi refuses to delete stacks that still have resources. This is your safety net.
- Destroy first: Always run
destroybeforedeleteto properly clean up cloud resources. - State loss: Once deleted, you lose all stack history, outputs, and configuration. No undo.
- Force flag danger: Using
--forcebypasses resource checks. Only use if you're absolutely certain resources are gone or managed elsewhere. - Orphaned resources: If you force-delete a stack with resources, those resources become orphaned (unmanaged by Pulumi).
Difference: destroy vs delete:
destroy:
- Tears down cloud resources (VMs, databases, etc.)
- Leaves stack metadata intact
- Updates state to reflect empty stack
- Resources are gone, but Pulumi still tracks the stack
delete (rm):
- Removes stack metadata from backend
- Does NOT touch cloud resources
- Pulumi stops tracking this stack entirely
- Used AFTER destroy to clean up metadata
Recommended Workflow:
# Step 1: Verify what resources exist
project-planton pulumi preview --manifest my-stack.yaml
# Step 2: Destroy the cloud resources
project-planton pulumi destroy --manifest my-stack.yaml
# Step 3: Verify resources are gone (should show empty stack)
project-planton pulumi preview --manifest my-stack.yaml
# Step 4: Remove the stack metadata
project-planton pulumi delete --manifest my-stack.yaml
# Done! Stack and resources are completely gone
When to use --force:
# Scenario 1: Stack state is corrupted, resources already manually deleted
# You know resources are gone but Pulumi state is wrong
project-planton pulumi delete --manifest broken-stack.yaml --force
# Scenario 2: Resources were imported/migrated to another stack
# Original stack should no longer manage them
project-planton pulumi delete --manifest old-stack.yaml --force
# Scenario 3: Test/development stack with resources you don't care about
# (Still not recommended - better to destroy properly)
project-planton pulumi delete --manifest test-stack.yaml --force
Best Practices:
# ✅ Good: Complete cleanup workflow
project-planton pulumi destroy --manifest stack.yaml --yes
project-planton pulumi delete --manifest stack.yaml
# ⚠️ Risky: Forcing without verification
project-planton pulumi delete --manifest stack.yaml --force
# ✅ Good: Verify stack FQDN before deleting
pulumi stack --stack <stack-fqdn> # Check what's in the stack
project-planton pulumi delete --manifest stack.yaml
# ✅ Good: Export state before deleting (backup)
pulumi stack export --stack <stack-fqdn> > backup.json
project-planton pulumi delete --manifest stack.yaml
Troubleshooting:
Error: "Stack still has resources"
# Problem: Trying to delete stack with resources
# Solution: Destroy resources first
project-planton pulumi destroy --manifest stack.yaml
project-planton pulumi delete --manifest stack.yaml
# Or if resources are actually gone (state is wrong)
project-planton pulumi refresh --manifest stack.yaml # Sync state
project-planton pulumi delete --manifest stack.yaml
Error: "Stack not found"
# Problem: Stack already deleted or never existed
# Solution: Verify stack FQDN
pulumi stack ls # List all stacks
# If not listed, it's already gone (nothing to do)
Common Flags
All commands support these flags. They're like the universal remote for infrastructure management.
Manifest Input
--manifest <file>: Path to your resource manifest YAML file.
project-planton pulumi up --manifest ops/resources/my-resource.yaml
--kustomize-dir <dir> + --overlay <name>: Use kustomize for environment-specific configurations.
# Loads kustomize base + overlays/prod
project-planton pulumi up \
--kustomize-dir backend/services/api \
--overlay prod
Priority: --manifest > --kustomize-dir + --overlay
Execution Control
--module-dir <path>: Override the Pulumi module directory (defaults to current directory).
# Use local development module instead of released version
project-planton pulumi up \
--manifest my-resource.yaml \
--module-dir ~/projects/custom-modules/my-module
--stack <org>/<project>/<stack>: Explicitly specify stack FQDN (overrides manifest label).
project-planton pulumi up \
--manifest resource.yaml \
--stack my-org/my-project/custom-stack-name
--yes: Auto-approve without confirmation prompts (for CI/CD).
project-planton pulumi up --manifest resource.yaml --yes
--force: Force removal of stack even if resources exist (only for delete/rm command).
# Use with extreme caution - only when you're certain resources are gone
project-planton pulumi delete --manifest resource.yaml --force
--set <key>=<value>: Override manifest values at runtime (repeatable flag).
project-planton pulumi up \
--manifest deployment.yaml \
--set spec.replicas=10 \
--set spec.container.image.tag=v2.1.0 \
--set metadata.env=staging
Credential Injection
These flags inject provider credentials (alternative to environment variables):
--aws-credential <file>: Path to AWS credential YAML--azure-credential <file>: Path to Azure credential YAML--gcp-credential <file>: Path to GCP credential YAML--kubernetes-cluster <file>: Path to Kubernetes cluster credential YAML--confluent-credential <file>: Path to Confluent Cloud credential YAML--docker-credential <file>: Path to Docker registry credential YAML--mongodb-atlas-credential <file>: Path to MongoDB Atlas credential YAML--snowflake-credential <file>: Path to Snowflake credential YAML
Example:
project-planton pulumi up \
--manifest ops/aws-resources/vpc.yaml \
--aws-credential ~/.config/planton/credentials/aws-prod.yaml
Common Workflows
First-Time Deployment
# 1. Initialize the stack (creates state tracking)
project-planton pulumi init --manifest my-resource.yaml
# 2. Preview what will be created
project-planton pulumi preview --manifest my-resource.yaml
# 3. Deploy the infrastructure
project-planton pulumi up --manifest my-resource.yaml
Shortcut: up creates the stack automatically if it doesn't exist:
# One command to rule them all (for new stacks)
project-planton pulumi up --manifest my-resource.yaml
Updating Existing Infrastructure
# 1. Edit your manifest
vim ops/resources/my-app.yaml
# 2. Preview the changes
project-planton pulumi preview --manifest ops/resources/my-app.yaml
# 3. Apply if changes look good
project-planton pulumi up --manifest ops/resources/my-app.yaml
Testing Configuration Changes
# Preview with overrides (no changes to manifest file)
project-planton pulumi preview \
--manifest api-deployment.yaml \
--set spec.replicas=20 \
--set spec.resources.limits.cpu=4000m
# If it looks good, apply with same overrides
project-planton pulumi up \
--manifest api-deployment.yaml \
--set spec.replicas=20 \
--set spec.resources.limits.cpu=4000m
# Later, commit the changes to manifest
vim api-deployment.yaml # Make changes permanent
Emergency Rollback
# Scenario: v2.0.0 deployment has issues, need to roll back to v1.9.5
# Option 1: Override the current manifest
project-planton pulumi up \
--manifest deployment.yaml \
--set spec.container.image.tag=v1.9.5
# Option 2: Revert manifest to previous version
git checkout HEAD~1 deployment.yaml
project-planton pulumi up --manifest deployment.yaml
# Option 3: Use a previous Git revision
git show HEAD~5:deployment.yaml > /tmp/previous-deployment.yaml
project-planton pulumi up --manifest /tmp/previous-deployment.yaml
Syncing After Manual Changes
# Someone made changes via AWS console, need to sync state
# 1. Refresh to see what changed
project-planton pulumi refresh --manifest s3-bucket.yaml
# 2. Review the diff
project-planton pulumi preview --manifest s3-bucket.yaml
# 3. Decide:
# - Changes match manifest? → Do nothing, state is synced
# - Changes don't match? → Update manifest or revert via `up`
# 4. If reverting manual changes:
project-planton pulumi up --manifest s3-bucket.yaml # Restores manifest config
Multi-Environment Deployment
# Using kustomize overlays for different environments
# Deploy to dev
project-planton pulumi up \
--kustomize-dir services/api \
--overlay dev
# Preview staging changes
project-planton pulumi preview \
--kustomize-dir services/api \
--overlay staging
# Deploy to production (with extra caution)
project-planton pulumi preview \
--kustomize-dir services/api \
--overlay prod
# Review carefully...
project-planton pulumi up \
--kustomize-dir services/api \
--overlay prod
Local Module Development
# Testing changes to Pulumi module code without publishing
cd ~/projects/project-planton/apis/.../.../iac/pulumi
# Point to local module directory
project-planton pulumi preview \
--manifest ~/manifests/test-resource.yaml \
--module-dir .
# Iterate: edit module code, run preview again
vim module/main.go
project-planton pulumi preview \
--manifest ~/manifests/test-resource.yaml \
--module-dir .
# Deploy with local module
project-planton pulumi up \
--manifest ~/manifests/test-resource.yaml \
--module-dir .
CI/CD Pipeline
#!/bin/bash
# deploy.sh - Automated deployment script
set -e # Exit on error
MANIFEST="ops/resources/app-${ENV}.yaml"
echo "🔍 Previewing changes..."
project-planton pulumi preview --manifest "$MANIFEST" --yes
echo "🚀 Deploying infrastructure..."
project-planton pulumi up --manifest "$MANIFEST" --yes
echo "✅ Deployment complete"
GitHub Actions Example:
name: Deploy Infrastructure
on:
push:
branches: [main]
paths: ['ops/resources/**']
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Pulumi
run: |
pulumi login ${{ secrets.PULUMI_BACKEND_URL }}
env:
PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
- name: Deploy Resources
run: |
project-planton pulumi up \
--manifest ops/resources/prod-infra.yaml \
--yes
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Troubleshooting
Error: "no stack named '...' found"
Symptom: Command fails with stack not found error.
Cause: The Pulumi stack hasn't been initialized yet.
Solution:
# Option 1: Run init first
project-planton pulumi init --manifest my-resource.yaml
project-planton pulumi preview --manifest my-resource.yaml
# Option 2: Use 'up' which auto-creates stack
project-planton pulumi up --manifest my-resource.yaml
Error: "another update is currently in progress"
Symptom: Command fails saying stack is locked.
Cause: A previous operation crashed or is still running, leaving the stack locked.
Solution:
# Check if operation is actually running
pulumi stack --stack <stack-fqdn>
# If no operation is running, cancel the lock
pulumi cancel --stack <stack-fqdn>
# Then retry your operation
project-planton pulumi up --manifest my-resource.yaml
Provider Authentication Failures
Symptom: "failed to create provider" or authentication errors.
Causes: Missing or invalid cloud provider credentials.
Solutions:
For AWS:
# Check credentials
aws sts get-caller-identity
# Or provide credential file
project-planton pulumi up \
--manifest resource.yaml \
--aws-credential ~/.aws/credentials-prod.yaml
For GCP:
# Check credentials
gcloud auth list
gcloud config get-value project
# Or set environment variable
export GOOGLE_APPLICATION_CREDENTIALS=~/gcp-key.json
project-planton pulumi up --manifest resource.yaml
For Cloudflare:
# Set API token
export CLOUDFLARE_API_TOKEN="your-token-here"
project-planton pulumi up --manifest resource.yaml
Preview Shows Unexpected Changes
Symptom: preview shows modifications you didn't make.
Causes:
- Someone made manual changes outside Pulumi
- Provider API defaults changed
- Computed values changed upstream
Solution:
# First, sync state with reality
project-planton pulumi refresh --manifest resource.yaml
# Then preview again
project-planton pulumi preview --manifest resource.yaml
# Compare against previous state
pulumi stack --show-urns --stack <stack-fqdn>
State Conflict: Resources Already Exist
Symptom: "resource already exists" error during deployment.
Cause: Resources exist in cloud but not in Pulumi state (created outside Pulumi or state lost).
Solution:
# Option 1: Import existing resources (advanced)
pulumi import <type> <name> <cloud-resource-id> --stack <stack-fqdn>
# Option 2: Manually delete cloud resources
# (Use cloud provider console/CLI to delete conflicting resources)
# Option 3: Use different resource names in manifest
vim my-resource.yaml # Change metadata.name or resource IDs
Best Practices
1. Always Preview Before Applying
# ✅ Good: Review changes first
project-planton pulumi preview --manifest resource.yaml
# Read output, verify changes look correct
project-planton pulumi up --manifest resource.yaml
# ⚠️ Risky: Blind deployment
project-planton pulumi up --manifest resource.yaml --yes
Why: Preview is your safety net. It catches mistakes before they become expensive incidents.
2. Use Version Control for Manifests
# ✅ Good: Track changes in Git
git add ops/resources/my-resource.yaml
git commit -m "feat: increase database instance size"
git push
# Deploy via CI/CD or manually
# ❌ Bad: Direct edits without version control
vim /tmp/my-resource.yaml
project-planton pulumi up --manifest /tmp/my-resource.yaml
Why: Version control gives you change history, rollback capability, and code review.
3. Refresh Before Important Operations
# ✅ Good: Sync state before major changes
project-planton pulumi refresh --manifest resource.yaml --yes
project-planton pulumi up --manifest resource.yaml
# ⚠️ Risky: Operating on stale state
# (Someone made manual changes you don't know about)
project-planton pulumi up --manifest resource.yaml
Why: Refreshing prevents conflicts and ensures you're working with accurate state.
4. Use Descriptive Stack Names
# ✅ Good: Clear, hierarchical naming
metadata:
labels:
pulumi.project-planton.org/stack.name: "planton-cloud/planton-cloud/prod.CloudflareR2Bucket.pipeline-logs"
# └─────org────┘ └─project──┘ └─────environment.ResourceType.resource-name───┘
# ❌ Bad: Generic, unclear names
metadata:
labels:
pulumi.project-planton.org/stack.name: "org1/proj1/stack1"
Why: Good names make it obvious what infrastructure the stack manages.
5. Test Changes in Lower Environments First
# ✅ Good: Progressive deployment
project-planton pulumi up --kustomize-dir services/api --overlay dev
# Test in dev...
project-planton pulumi up --kustomize-dir services/api --overlay staging
# Test in staging...
project-planton pulumi up --kustomize-dir services/api --overlay prod
# ❌ Bad: YOLO to production
project-planton pulumi up --kustomize-dir services/api --overlay prod --yes
Why: Lower environments catch issues before they impact production.
6. Use --set for Temporary Overrides Only
# ✅ Good: Quick testing
project-planton pulumi preview \
--manifest deployment.yaml \
--set spec.replicas=1 # Test with minimal resources
# ❌ Bad: Permanent changes via flag
# (6 months later: "Why is prod running 1 replica?!")
project-planton pulumi up \
--manifest deployment.yaml \
--set spec.replicas=1 \
--yes
Why: Flags don't persist. Commit important changes to your manifest.
7. Document Provider Credentials
# ✅ Good: Document in README
# ops/README.md
# Deploy with:
# export CLOUDFLARE_API_TOKEN=$(pass cloudflare/api-token)
# project-planton pulumi up --manifest r2-bucket.yaml
# ⚠️ Bad: Tribal knowledge
# (New team member: "How do I deploy this?")
Why: Documentation prevents "works on my machine" situations.
8. Clean Up Unused Stacks
# After destroying resources, remove the empty stack
project-planton pulumi destroy --manifest temp-resource.yaml --yes
pulumi stack rm <stack-fqdn> # Remove stack metadata
# List all stacks to find abandoned ones
pulumi stack ls
Why: Stack proliferation makes Pulumi backend harder to manage.
Tips & Tricks
Quick Stack Status Check
# View current stack state
pulumi stack --stack <stack-fqdn>
# See all resources in stack
pulumi stack --show-urns --stack <stack-fqdn>
# View outputs
pulumi stack output --stack <stack-fqdn>
Diff Specific Resources
# Preview changes, grep for specific resource
project-planton pulumi preview --manifest resource.yaml 2>&1 | grep "aws:s3:Bucket"
Copy Stack Outputs to Clipboard
# macOS
pulumi stack output connection_string --stack <stack-fqdn> | pbcopy
# Linux
pulumi stack output connection_string --stack <stack-fqdn> | xclip -selection clipboard
Automated Health Checks Post-Deployment
# deploy-and-verify.sh
project-planton pulumi up --manifest api-deployment.yaml --yes
# Wait for pods to be ready
kubectl rollout status deployment/api -n production
# Run smoke tests
curl -f https://api.example.com/health || exit 1
Export Stack for Disaster Recovery
# Export current state
pulumi stack export --stack <stack-fqdn> > stack-backup-$(date +%Y%m%d).json
# Import if state gets corrupted
pulumi stack import --stack <stack-fqdn> < stack-backup-20250105.json
Related Documentation
- Pulumi Concepts - Official Pulumi documentation
- Manifest Structure Guide - Understanding Project Planton manifests
- Credentials Guide - Setting up cloud provider credentials
- CLI Reference - Complete CLI command reference
Getting Help
Found a bug? Open an issue
Need support? Check existing issues or discussions
Contributing? Pull requests welcome!
Remember: Infrastructure as code is code. Apply the same discipline you'd apply to application code—version control, testing, code review, and automation. Your infrastructure deserves it. 🚀
Next article