pulumi-automation-api

Programmatic orchestration of Pulumi infrastructure operations across multiple stacks and applications. Supports both local source (existing Pulumi projects) and inline source (embedded programs) architectures, enabling flexible deployment patterns from simple to complex multi-stack scenarios Handles multi-stack orchestration with dependency sequencing, parallel independent deployments, and cross-stack output passing for coordinated infrastructure provisioning Provides programmatic configuration management, real-time output streaming, and error handling as alternatives to CLI-based workflows Language-independent orchestration allows the automation layer to use a different language than the Pulumi programs it manages, enabling platform and application team separation

INSTALLATION
npx skills add https://github.com/pulumi/agent-skills --skill pulumi-automation-api
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2a

// Create or select a stack

const stack = await automation.LocalWorkspace.createOrSelectStack({

stackName: "dev",

projectName: "my-project",

program: async () => {

// Your Pulumi program here

},

});

// Run pulumi up programmatically

const upResult = await stack.up({ onOutput: console.log });

console.log(Update summary: ${JSON.stringify(upResult.summary)});

## When to Use Automation API

### Good Use Cases

**Multi-stack orchestration:**

When you split infrastructure into multiple focused projects, Automation API helps offset the added complexity by orchestrating operations across stacks:

infrastructure → platform → application

↓ ↓ ↓

(VPC) (Kubernetes) (Services)


Automation API ensures correct sequencing without manual intervention.

**Self-service platforms:**

Build internal tools where developers request infrastructure without learning Pulumi:

- Web portals for environment provisioning

- Slack bots that create/destroy resources

- Custom CLIs tailored to your organization

**Embedded infrastructure:**

Applications that provision their own infrastructure:

- SaaS platforms creating per-tenant resources

- Testing frameworks spinning up test environments

- CI/CD systems with dynamic infrastructure needs

**Replacing fragile scripts:**

If you have Bash scripts or Makefiles stitching together multiple `pulumi` commands, Automation API provides:

- Proper error handling

- Type safety

- Programmatic access to outputs

### When NOT to Use

- Single project with standard deployment needs

- When you don't need programmatic control over operations

## Architecture Choices

### Local Source vs Inline Source

**Local Source** - Pulumi program in separate files:

const stack = await automation.LocalWorkspace.createOrSelectStack({

stackName: "dev",

workDir: "./infrastructure", // Points to existing Pulumi project

});


**When to use:**

- Different teams maintain orchestrator vs Pulumi programs

- Pulumi programs already exist

- Want independent version control and release cycles

- Platform team orchestrating application team's infrastructure

**Inline Source** - Pulumi program embedded in orchestrator:

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

const stack = await automation.LocalWorkspace.createOrSelectStack({

stackName: "dev",

projectName: "my-project",

program: async () => {

const bucket = new aws.s3.Bucket("my-bucket");

return { bucketName: bucket.id };

},

});


**When to use:**

- Single team owns everything

- Tight coupling between orchestration and infrastructure is desired

- Distributing as compiled binary (no source files needed)

- Simpler deployment artifact

### Language Independence

The Automation API program can use a different language than the Pulumi programs it orchestrates:

Orchestrator (Go) → manages → Pulumi Program (TypeScript)


This enables platform teams to use their preferred language while application teams use theirs.

## Common Patterns

### Multi-Stack Orchestration

Deploy multiple stacks in dependency order:

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

async function deploy() {

const stacks = [

{ name: "infrastructure", dir: "./infra" },

{ name: "platform", dir: "./platform" },

{ name: "application", dir: "./app" },

];

for (const stackInfo of stacks) {

console.log(Deploying ${stackInfo.name}...);

const stack = await automation.LocalWorkspace.createOrSelectStack({

stackName: "prod",

workDir: stackInfo.dir,

});

await stack.up({ onOutput: console.log });

console.log(${stackInfo.name} deployed successfully);

}

}

async function destroy() {

// Destroy in reverse order

const stacks = [

{ name: "application", dir: "./app" },

{ name: "platform", dir: "./platform" },

{ name: "infrastructure", dir: "./infra" },

];

for (const stackInfo of stacks) {

console.log(Destroying ${stackInfo.name}...);

const stack = await automation.LocalWorkspace.selectStack({

stackName: "prod",

workDir: stackInfo.dir,

});

await stack.destroy({ onOutput: console.log });

}

}


### Passing Configuration

Set stack configuration programmatically:

const stack = await automation.LocalWorkspace.createOrSelectStack({

stackName: "dev",

workDir: "./infrastructure",

});

// Set configuration values

await stack.setConfig("aws:region", { value: "us-west-2" });

await stack.setConfig("dbPassword", { value: "secret", secret: true });

// Then deploy

await stack.up();


### Reading Outputs

Access stack outputs after deployment:

const upResult = await stack.up();

// Get all outputs

const outputs = await stack.outputs();

console.log(VPC ID: ${outputs["vpcId"].value});

// Or from the up result

console.log(Outputs: ${JSON.stringify(upResult.outputs)});


### Error Handling

Handle deployment failures gracefully:

try {

const result = await stack.up({ onOutput: console.log });

if (result.summary.result === "failed") {

console.error("Deployment failed");

process.exit(1);

}

} catch (error) {

console.error(Deployment error: ${error});

throw error;

}


### Parallel Stack Operations

When stacks are independent, deploy in parallel:

const independentStacks = [

{ name: "service-a", dir: "./service-a" },

{ name: "service-b", dir: "./service-b" },

{ name: "service-c", dir: "./service-c" },

];

await Promise.all(independentStacks.map(async (stackInfo) => {

const stack = await automation.LocalWorkspace.createOrSelectStack({

stackName: "prod",

workDir: stackInfo.dir,

});

return stack.up({ onOutput: (msg) => console.log([${stackInfo.name}] ${msg}) });

}));


## Best Practices

### Separate Configuration from Code

Externalize configuration into files or environment variables:

import * as fs from "fs";

interface DeployConfig {

stacks: Array<{ name: string; dir: string; }>;

environment: string;

}

const config: DeployConfig = JSON.parse(

fs.readFileSync("./deploy-config.json", "utf-8")

);

for (const stackInfo of config.stacks) {

const stack = await automation.LocalWorkspace.createOrSelectStack({

stackName: config.environment,

workDir: stackInfo.dir,

});

await stack.up();

}


This enables distributing compiled binaries without exposing source code.

### Stream Output for Long Operations

Use `onOutput` callback for real-time feedback:

await stack.up({

onOutput: (message) => {

process.stdout.write(message);

// Or send to logging system, websocket, etc.

},

});

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card