Claude Code for Pulumi: Infrastructure as Real Code with TypeScript and Python — Claude Skills 360 Blog
Blog / Infrastructure / Claude Code for Pulumi: Infrastructure as Real Code with TypeScript and Python
Infrastructure

Claude Code for Pulumi: Infrastructure as Real Code with TypeScript and Python

Published: October 17, 2026
Read time: 8 min read
By: Claude Skills 360

Pulumi lets you write infrastructure in real programming languages — TypeScript, Python, Go, C# — with loops, conditionals, and abstractions that HCL doesn’t support naturally. The tradeoff vs Terraform: more expressiveness, but more complexity. Pulumi excels when your infrastructure has programmatic patterns: creating 20 similar resources, generating configs from API calls, or using your existing type system to validate infrastructure inputs. Claude Code writes Pulumi programs, component resources for reusability, and tests infrastructure with mocks.

CLAUDE.md for Pulumi Projects

## Pulumi Stack
- Language: TypeScript (strict mode)
- Runtime: Pulumi 3.x
- State backend: Pulumi Cloud (or S3 for self-managed)
- Stack naming: {project}-{environment} e.g., order-api-production
- Environments: preview, staging, production — separate stacks
- Secrets: pulumi config set --secret (never plaintext in Pulumi configs)
- Outputs: export everything needed by dependent stacks
- Testing: @pulumi/pulumi/testing for mock-based unit tests
- No dynamic providers unless absolutely necessary — prefer static resource definitions

TypeScript Infrastructure Program

// index.ts — production ECS + RDS setup
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import { ProductionVpc } from "./components/vpc";
import { PostgresCluster } from "./components/postgres";
import { EcsService } from "./components/ecs";

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

// Type-safe config with defaults
const appConfig = {
  dbInstanceClass: config.get("dbInstanceClass") ?? (environment === "production" ? "db.r6g.large" : "db.t3.medium"),
  desiredCount: config.getNumber("desiredCount") ?? (environment === "production" ? 3 : 1),
  domainName: config.require("domainName"),
};

// Reusable component: VPC
const vpc = new ProductionVpc("main", {
  environment,
  cidr: "10.0.0.0/16",
  azCount: environment === "production" ? 3 : 2,
});

// Reusable component: PostgreSQL
const db = new PostgresCluster("orders", {
  environment,
  vpcId: vpc.vpcId,
  privateSubnetIds: vpc.privateSubnetIds,
  instanceClass: appConfig.dbInstanceClass,
  multiAz: environment === "production",
});

// Reusable component: ECS Service
const api = new EcsService("order-api", {
  environment,
  vpcId: vpc.vpcId,
  publicSubnetIds: vpc.publicSubnetIds,
  privateSubnetIds: vpc.privateSubnetIds,
  desiredCount: appConfig.desiredCount,
  
  image: config.require("imageTag").apply(tag => 
    `123456789.dkr.ecr.us-east-1.amazonaws.com/order-api:${tag}`
  ),
  
  environment: {
    NODE_ENV: "production",
    PORT: "8080",
  },
  secrets: {
    DATABASE_URL: db.connectionStringSecretArn,
  },
  
  domainName: appConfig.domainName,
});

// Exports: used by other stacks via stack references
export const serviceUrl = api.serviceUrl;
export const dbEndpoint = db.endpoint;
export const vpcId = vpc.vpcId;

Component Resource

// components/postgres.ts — reusable PostgreSQL component
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as random from "@pulumi/random";

interface PostgresClusterArgs {
  environment: string;
  vpcId: pulumi.Input<string>;
  privateSubnetIds: pulumi.Input<string[]>;
  instanceClass: string;
  multiAz: boolean;
}

export class PostgresCluster extends pulumi.ComponentResource {
  public readonly endpoint: pulumi.Output<string>;
  public readonly connectionStringSecretArn: pulumi.Output<string>;
  public readonly securityGroupId: pulumi.Output<string>;
  
  constructor(name: string, args: PostgresClusterArgs, opts?: pulumi.ComponentResourceOptions) {
    super("myorg:database:PostgresCluster", name, {}, opts);
    
    const namePrefix = `${args.environment}-${name}`;
    
    // Generate password
    const password = new random.RandomPassword(`${namePrefix}-pwd`, {
      length: 32,
      special: true,
      overrideSpecial: "!#$%^&*()-_=+",
    }, { parent: this });
    
    // Subnet group
    const subnetGroup = new aws.rds.SubnetGroup(`${namePrefix}-subnet`, {
      subnetIds: args.privateSubnetIds,
      tags: { Environment: args.environment },
    }, { parent: this });
    
    // Security group
    const sg = new aws.ec2.SecurityGroup(`${namePrefix}-sg`, {
      vpcId: args.vpcId,
      description: `RDS security group for ${namePrefix}`,
      tags: { Environment: args.environment, Name: `${namePrefix}-rds` },
    }, { parent: this });
    
    // RDS instance
    const db = new aws.rds.Instance(namePrefix, {
      identifier: namePrefix,
      engine: "postgres",
      engineVersion: "16.1",
      instanceClass: args.instanceClass,
      allocatedStorage: args.environment === "production" ? 100 : 20,
      dbName: name.replace(/-/g, "_"),
      username: "dbadmin",
      password: password.result,
      multiAz: args.multiAz,
      dbSubnetGroupName: subnetGroup.name,
      vpcSecurityGroupIds: [sg.id],
      deletionProtection: args.environment === "production",
      skipFinalSnapshot: args.environment !== "production",
      storageEncrypted: true,
      backupRetentionPeriod: args.environment === "production" ? 35 : 7,
      tags: { Environment: args.environment, ManagedBy: "pulumi" },
    }, { parent: this, deleteBeforeReplace: true });
    
    // Store credentials in Secrets Manager
    const secret = new aws.secretsmanager.Secret(`${namePrefix}-secret`, {
      name: `${namePrefix}-db-credentials`,
      recoveryWindowInDays: args.environment === "production" ? 30 : 0,
    }, { parent: this });
    
    new aws.secretsmanager.SecretVersion(`${namePrefix}-secret-version`, {
      secretId: secret.id,
      secretString: pulumi.all([db.address, db.port, password.result]).apply(
        ([host, port, pwd]) => JSON.stringify({
          username: "dbadmin",
          password: pwd,
          host,
          port,
          dbname: name.replace(/-/g, "_"),
        })
      ),
    }, { parent: this });
    
    this.endpoint = db.endpoint;
    this.connectionStringSecretArn = secret.arn;
    this.securityGroupId = sg.id;
    
    this.registerOutputs({
      endpoint: this.endpoint,
      connectionStringSecretArn: this.connectionStringSecretArn,
      securityGroupId: this.securityGroupId,
    });
  }
}

Stack References (Cross-Stack Data)

// Reference outputs from another Pulumi stack
const networkStack = new pulumi.StackReference(`myorg/network/${environment}`);

const vpcId = networkStack.getOutput("vpcId");
const privateSubnetIds = networkStack.getOutput("privateSubnetIds");

// Use in this stack
const db = new PostgresCluster("orders", {
  vpcId,
  privateSubnetIds: privateSubnetIds as pulumi.Output<string[]>,
  // ...
});

Automation API

// deploy.ts — programmatic Pulumi deployment (for CI or custom tooling)
import { LocalWorkspace, Stack, UpResult } from "@pulumi/pulumi/automation";
import * as path from "path";

async function deployStack(environment: string, imageTag: string): Promise<UpResult> {
  const stackName = `myorg/order-api/${environment}`;
  
  const stack = await LocalWorkspace.createOrSelectStack({
    stackName,
    workDir: path.join(__dirname, ".."),  // Where index.ts is
  });
  
  // Set config programmatically
  await stack.setAllConfig({
    "imageTag": { value: imageTag },
    "domainName": { value: `api.${environment}.myapp.com` },
  });
  
  console.log(`Deploying ${stackName} with image ${imageTag}...`);
  
  const result = await stack.up({
    onOutput: (msg) => process.stdout.write(msg),
    parallel: 10,
  });
  
  console.log(`Deploy complete: ${result.summary.kind}`);
  return result;
}

// In CI: called after Docker image is built and pushed
const imageTag = process.env.IMAGE_TAG!;
const environment = process.env.ENVIRONMENT!;
await deployStack(environment, imageTag);

Testing with Mocks

// tests/postgres.test.ts
import * as pulumi from "@pulumi/pulumi";

// Mock all AWS resources
pulumi.runtime.setMocks({
  newResource: (args: pulumi.runtime.MockResourceArgs) => {
    return {
      id: `${args.name}-mock-id`,
      state: { ...args.inputs, id: `${args.name}-mock-id` },
    };
  },
  call: (args: pulumi.runtime.MockCallArgs) => {
    return args.inputs;
  },
});

describe("PostgresCluster", () => {
  let cluster: PostgresCluster;
  
  beforeEach(() => {
    cluster = new PostgresCluster("test", {
      environment: "test",
      vpcId: "vpc-mock",
      privateSubnetIds: ["subnet-1", "subnet-2"],
      instanceClass: "db.t3.micro",
      multiAz: false,
    });
  });
  
  it("enables deletion protection in production", async () => {
    const prodCluster = new PostgresCluster("prod-test", {
      environment: "production",
      vpcId: "vpc-mock",
      privateSubnetIds: ["subnet-1"],
      instanceClass: "db.r6g.large",
      multiAz: true,
    });
    
    // Get the RDS instance from the component
    const instance = prodCluster["db"] as aws.rds.Instance;
    const deletionProtection = await promiseOf(instance.deletionProtection);
    expect(deletionProtection).toBe(true);
  });
  
  it("skips final snapshot for non-production", async () => {
    const instance = cluster["db"] as aws.rds.Instance;
    const skipSnapshot = await promiseOf(instance.skipFinalSnapshot);
    expect(skipSnapshot).toBe(true);
  });
});

function promiseOf<T>(output: pulumi.Output<T>): Promise<T> {
  return new Promise(resolve => output.apply(resolve));
}

For the Terraform alternative to Pulumi for teams preferring HCL, the Terraform advanced guide covers modules and remote state. For the Nix environments that can manage Pulumi CLI and provider versions hermetically, the Nix guide covers dev shell setup. The Claude Skills 360 bundle includes infrastructure skill sets covering Pulumi components, Automation API, and infrastructure testing. Start with the free tier to try Pulumi component generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free