Pulumi Projects & Stacks

Easy 18 min read

Understanding Projects

Why Projects & Stacks Matter

The Problem: As infrastructure grows, you need a way to organize code and deploy the same infrastructure to multiple environments without duplication.

The Solution: Pulumi projects define what infrastructure looks like, while stacks let you deploy multiple instances (dev, staging, prod) with different configurations.

Real Impact: Teams can safely test infrastructure changes in dev before promoting to production, all from the same codebase.

Real-World Analogy

Think of projects and stacks like a cookie cutter and cookies:

  • Project = The cookie cutter (the shape/template)
  • Stack = Each individual cookie (an instance of that template)
  • Configuration = The dough flavor (chocolate for dev, vanilla for prod)
  • Pulumi.yaml = The recipe card that identifies the cookie cutter
  • Stack Outputs = The label on each cookie box telling you what's inside

What is a Pulumi Project?

Single Codebase

A project is a directory containing your Pulumi program, identified by a Pulumi.yaml file at the root.

Language Runtime

Each project specifies a runtime (nodejs, python, go, dotnet, java) that determines how code is executed.

Multiple Stacks

A single project can have many stacks, each representing an isolated deployment environment.

Version Controlled

Projects live in your Git repository alongside application code, enabling GitOps workflows.

Project Structure

project-layout
my-infrastructure/
├── Pulumi.yaml              # Project definition
├── Pulumi.dev.yaml           # Dev stack config
├── Pulumi.staging.yaml       # Staging stack config
├── Pulumi.prod.yaml          # Production stack config
├── index.ts                  # Main program entry point
├── network.ts                # Network resources
├── database.ts               # Database resources
├── package.json              # Node.js dependencies
└── tsconfig.json             # TypeScript config

The Pulumi.yaml File

Pulumi.yaml
name: my-infrastructure
runtime:
  name: nodejs
  options:
    typescript: true
description: Core infrastructure for our application
config:
  pulumi:tags:
    value:
      pulumi:template: aws-typescript

Working with Stacks

Project to Stacks Relationship
Project: my-infrastructure Pulumi.yaml + index.ts Stack: dev t3.micro instances 1 AZ db.t3.small RDS us-east-1 Stack: staging t3.small instances 2 AZs db.t3.medium RDS us-east-1 Stack: prod t3.large instances 3 AZs db.r5.xlarge RDS us-east-1 + us-west-2

Creating and Managing Stacks

stack-management.sh
# Create a new stack
pulumi stack init dev
pulumi stack init staging
pulumi stack init prod

# List all stacks
pulumi stack ls

# Switch between stacks
pulumi stack select dev

# View current stack
pulumi stack

# Remove a stack (must destroy resources first)
pulumi stack rm dev

Stack Configuration

Pulumi.dev.yaml
config:
  aws:region: us-east-1
  my-infrastructure:instanceType: t3.micro
  my-infrastructure:minInstances: "1"
  my-infrastructure:dbInstanceClass: db.t3.small

Reading Configuration in Code

index.ts
import * as pulumi from "@pulumi/pulumi";

const config = new pulumi.Config();

// Required config (throws if missing)
const instanceType = config.require("instanceType");

// Optional config with default
const minInstances = config.getNumber("minInstances") || 1;

// Stack name for resource naming
const stack = pulumi.getStack();  // "dev", "staging", "prod"

// Use stack name in resource names
const bucketName = `app-data-${stack}`;

Stack Outputs

outputs.sh
# View all outputs
pulumi stack output

# Get a specific output
pulumi stack output bucketName

# Get output as JSON
pulumi stack output --json

# Use output in scripts
BUCKET=$(pulumi stack output bucketName)
echo "Deploying to $BUCKET"

Multi-Stack Patterns

Common Multi-Stack Strategies

  • Environment-per-stack: dev, staging, prod stacks in one project (most common)
  • Region-per-stack: us-east-1, eu-west-1 stacks for multi-region deployments
  • Team-per-stack: team-a, team-b stacks sharing the same infrastructure pattern
  • Feature-branch stacks: Create ephemeral stacks for PR review environments
environment-aware.ts
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const stack = pulumi.getStack();
const config = new pulumi.Config();

// Environment-specific sizing
const isProd = stack === "prod";
const instanceType = isProd ? "t3.large" : "t3.micro";
const minSize = isProd ? 3 : 1;

// All resources automatically tagged with environment
const defaultTags = {
    Environment: stack,
    ManagedBy: "pulumi",
    Project: pulumi.getProject(),
};

Common Pitfall

Problem: Accidentally deploying to the wrong stack (e.g., destroying prod instead of dev).

Solution: Always run pulumi stack before pulumi up or pulumi destroy to confirm which stack is active. Use --stack flag for explicit targeting.

Quick Reference

Stack Management Commands

Command Description Example
pulumi stack init Create a new stack pulumi stack init prod
pulumi stack select Switch active stack pulumi stack select dev
pulumi stack ls List all stacks pulumi stack ls
pulumi stack output View stack outputs pulumi stack output vpcId
pulumi stack rm Remove a stack pulumi stack rm dev --yes
pulumi stack tag Manage stack tags pulumi stack tag set env dev
pulumi stack export Export stack state pulumi stack export --file state.json