Pulumi logo

pulumi

Infrastructure as code with programming languages

$ npx docs2skills add pulumi-infrastructure
SKILL.md
pulumi-esc.md
cloud-providers.md
testing-policies.md
deployment-workflows.md

Pulumi

Infrastructure as code with programming languages

What this skill does

Pulumi enables infrastructure as code using general-purpose programming languages (TypeScript, Python, Go, C#, Java, YAML) instead of domain-specific configuration languages. You define cloud resources using familiar programming constructs—loops, conditionals, functions, classes—and Pulumi provisions them across 150+ cloud providers. It maintains resource state, handles dependencies automatically, and provides a unified workflow for infrastructure lifecycle management.

Unlike template-based tools, Pulumi gives you the full power of programming languages for infrastructure logic, abstraction, and reusability. It supports multi-cloud deployments, policy enforcement, secrets management, and integrates with existing CI/CD pipelines while providing rich preview capabilities before applying changes.

Prerequisites

  • Pulumi CLI installed
  • Cloud provider credentials (AWS, Azure, GCP, etc.)
  • Runtime for chosen language (Node.js, Python, Go, .NET, Java)
  • Pulumi Cloud account (optional, can use local/S3/Azure backends)

Quick start

# Install Pulumi CLI
curl -fsSL https://get.pulumi.com | sh

# Create new project
pulumi new aws-typescript
cd my-project

# Configure AWS region
pulumi config set aws:region us-west-2

# Deploy infrastructure
pulumi up

Example TypeScript program:

import * as aws from "@pulumi/aws";

const bucket = new aws.s3.Bucket("my-bucket", {
    website: {
        indexDocument: "index.html",
    },
});

export const bucketName = bucket.id;
export const websiteUrl = bucket.websiteEndpoint;

Core concepts

Projects contain your infrastructure code and Pulumi.yaml configuration file. Stacks are isolated instances of your project (dev, staging, prod). Resources are cloud infrastructure components (VMs, databases, networks) defined in your program.

State tracks actual vs. desired resource configuration, stored in backends (Pulumi Cloud, S3, Azure Blob). Providers interface with cloud APIs—Pulumi supports 150+ providers. Components are reusable abstractions that encapsulate multiple resources.

Outputs are values computed after resource creation. Inputs can reference outputs from other resources, creating automatic dependency graphs. Secrets are encrypted values for sensitive data.

Key API surface

// Resource creation
new aws.ec2.Instance("web-server", { ... })

// Outputs and inputs
const bucket = new aws.s3.Bucket("bucket");
const bucketPolicy = new aws.s3.BucketPolicy("policy", {
    bucket: bucket.id, // Output -> Input dependency
});

// Configuration
pulumi.Config().require("dbPassword")
pulumi.Config().getSecret("apiKey")

// Stack references
pulumi.StackReference("org/project/stack")

// Components
class WebService extends pulumi.ComponentResource { ... }

// Functions (data sources)
aws.getAvailabilityZones()
aws.getCallerIdentity()

// Transformations
pulumi.runtime.registerStackTransformation(...)

Common patterns

Multi-tier application:

const vpc = new aws.ec2.Vpc("vpc", { cidrBlock: "10.0.0.0/16" });
const subnet = new aws.ec2.Subnet("subnet", {
    vpcId: vpc.id,
    cidrBlock: "10.0.1.0/24",
});

const db = new aws.rds.Instance("db", {
    engine: "postgres",
    dbSubnetGroupName: dbSubnetGroup.name,
});

const app = new aws.ec2.Instance("app", {
    subnetId: subnet.id,
    userData: pulumi.interpolate`#!/bin/bash
    export DB_HOST=${db.endpoint}`,
});

Reusable components:

export class StaticWebsite extends pulumi.ComponentResource {
    public readonly url: pulumi.Output<string>;
    
    constructor(name: string, args: { content: string }, opts?: pulumi.ComponentResourceOptions) {
        super("pkg:index:StaticWebsite", name, {}, opts);
        
        const bucket = new aws.s3.Bucket(`${name}-bucket`, {
            website: { indexDocument: "index.html" }
        }, { parent: this });
        
        this.url = bucket.websiteEndpoint;
        this.registerOutputs({ url: this.url });
    }
}

Stack references:

const infraStack = new pulumi.StackReference("myorg/infra/prod");
const vpcId = infraStack.getOutput("vpcId");

const app = new aws.ec2.Instance("app", {
    subnetId: infraStack.getOutput("subnetId"),
});

Configuration and secrets:

const config = new pulumi.Config();
const dbPassword = config.requireSecret("dbPassword");
const instanceType = config.get("instanceType") || "t3.micro";

const db = new aws.rds.Instance("db", {
    password: dbPassword,
    instanceClass: `db.${instanceType}`,
});

Configuration

Project file (Pulumi.yaml):

name: my-project
runtime: nodejs
description: My infrastructure project
config:
  aws:region:
    description: AWS region
    default: us-west-2

Stack configuration:

pulumi config set aws:region us-east-1
pulumi config set --secret dbPassword mySecretPassword
pulumi config set instanceCount 3

Environment variables:

  • PULUMI_ACCESS_TOKEN - Pulumi Cloud access token
  • PULUMI_CONFIG_PASSPHRASE - Local state encryption key
  • PULUMI_BACKEND_URL - Custom backend URL (s3://, azblob://, gs://)

Best practices

Use stack references to share data between projects instead of duplicating resources. Organize by lifecycle - separate long-lived infrastructure (VPCs, DNS) from applications.

Leverage components for reusable patterns. Export them as packages for organization-wide use. Use explicit names for resources to avoid auto-generated names that change on recreation.

Structure secrets properly - use pulumi config set --secret for sensitive values, never hardcode in source. Pin provider versions in dependencies to ensure reproducible deployments.

Use resource options like protect: true for critical resources, deleteBeforeReplace for zero-downtime updates. Implement proper error handling with resource options and stack policies.

Test infrastructure with unit tests using @pulumi/pulumi/testing or integration tests with temporary stacks. Use transformations to apply organization policies across all resources.

Gotchas and common mistakes

Outputs are promises - use .apply() or pulumi.interpolate to work with output values, never access them synchronously. bucket.id.substring(0, 5) fails; use bucket.id.apply(id => id.substring(0, 5)).

Resource names must be unique within a stack. Pulumi uses them for state tracking. Changing names creates new resources and deletes old ones.

Secrets in outputs - outputs referencing secrets become secret automatically, but applying non-secret operations can leak them. Use pulumi.unsecret() carefully.

State conflicts occur when multiple updates run simultaneously. Use pulumi cancel to stop conflicting updates, never edit state files manually.

Import existing resources with pulumi import before managing them. Creating resources with same names as existing ones causes conflicts.

Provider configuration timing - configure providers at program start, not during resource creation. Provider configs can't use resource outputs.

Dependency cycles happen when resources reference each other's outputs. Break cycles by removing unnecessary dependencies or using explicit dependsOn.

Stack outputs vs exports - export creates stack outputs; console.log() in the program only shows during preview/update, not in normal operations.

For cloud provider specifics and advanced patterns, read the cloud-providers.md file in this skill directory.

For Pulumi ESC environments and secrets management, read the pulumi-esc.md file in this skill directory.

For CI/CD integration and Pulumi Deployments, read the deployment-workflows.md file in this skill directory.

For testing strategies and policy enforcement, read the testing-policies.md file in this skill directory.