home / skills / pulumi / agent-skills / pulumi-automation-api

pulumi-automation-api skill

/authoring/skills/pulumi-automation-api

This skill helps you orchestrate Pulumi deployments across stacks and embed Pulumi in applications, boosting automation and self-service infrastructure.

npx playbooks add skill pulumi/agent-skills --skill pulumi-automation-api

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
8.4 KB
---
name: pulumi-automation-api
version: 1.0.0
description: Best practices for using Pulumi Automation API to programmatically orchestrate infrastructure operations. Covers multi-stack orchestration, embedding Pulumi in applications, architecture choices, and common patterns.
---

# Pulumi Automation API

## When to Use This Skill

Invoke this skill when:

- Orchestrating deployments across multiple Pulumi stacks
- Embedding Pulumi operations in custom applications
- Building self-service infrastructure platforms
- Replacing fragile Bash/Makefile orchestration scripts
- Creating custom CLIs for infrastructure management
- Building web applications that provision infrastructure

## What is Automation API

Automation API provides programmatic access to Pulumi operations. Instead of running `pulumi up` from the CLI, you call functions in your code that perform the same operations.

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

// 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:

```text
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:

```typescript
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:

```typescript
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:

```text
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
await stack.up({
    onOutput: (message) => {
        process.stdout.write(message);
        // Or send to logging system, websocket, etc.
    },
});
```

## Quick Reference

| Scenario | Approach |
| --- | --- |
| Existing Pulumi projects | Local source with workDir |
| New embedded infrastructure | Inline source with program function |
| Different teams | Local source for independence |
| Compiled binary distribution | Inline source or bundled local |
| Multi-stack dependencies | Sequential deployment in order |
| Independent stacks | Parallel deployment with Promise.all |

## Related Skills

- **pulumi-best-practices**: Code-level patterns for Pulumi programs

## References

- https://www.pulumi.com/docs/using-pulumi/automation-api/
- https://www.pulumi.com/docs/using-pulumi/automation-api/concepts-terminology/
- https://www.pulumi.com/blog/iac-recommended-practices-using-automation-api/

Overview

This skill documents best practices for using the Pulumi Automation API to programmatically orchestrate infrastructure operations. It focuses on multi-stack orchestration, embedding Pulumi programs in applications, architecture tradeoffs, and common patterns to replace brittle scripts. The guidance targets platform teams, application owners, and engineers building self-service or embedded provisioning workflows.

How this skill works

Automation API lets you run Pulumi operations (create, update, destroy, export outputs) from code instead of the CLI. It supports both LocalWorkspace (pointing to existing project files) and inline programs (embedded program functions), and exposes stack lifecycle methods such as createOrSelectStack, up, destroy, setConfig, and outputs. Use the API to sequence stacks, run independent stacks in parallel, stream real-time logs, and handle errors programmatically.

When to use it

  • Orchestrating deployments across multiple dependent Pulumi stacks
  • Embedding Pulumi into applications, CLIs, or web portals
  • Building self-service infrastructure platforms and internal tooling
  • Replacing fragile Bash/Makefile orchestration scripts
  • Creating per-tenant or test environments programmatically

Best practices

  • Separate configuration from code — load deploy manifests or env vars so compiled binaries don’t leak source-specific settings
  • Choose LocalSource when teams and lifecycle need independence; choose InlineSource for tight coupling or single-binary distribution
  • Stream output with onOutput for real-time feedback to logs, UIs, or CLIs during long operations
  • Handle failures explicitly: check up result summaries and catch exceptions to provide clear remediation steps
  • Orchestrate stacks sequentially when there are dependencies; use parallel execution only for independent stacks

Example use cases

  • Sequentially deploy infrastructure → platform → application stacks with correct ordering and rollback behavior
  • Build a web portal or Slack bot that provisions environments via Automation API and returns stack outputs
  • Embed Pulumi in a SaaS control plane to create per-tenant resources at signup
  • Replace Makefile orchestration with a typed program that sets config, runs up, reads outputs, and reports status
  • Run test suites that spin up ephemeral environments via inline programs and destroy them after tests

FAQ

When should I use inline programs vs local workDir projects?

Use inline when a single team owns orchestrator and infra or when distributing a compiled binary. Use local workDir when teams need independent version control, separate release cycles, or when existing Pulumi projects already exist.

Can I run stacks in parallel?

Yes. Run independent stacks in parallel (Promise.all or equivalent) to speed deployments. Avoid parallelism for stacks with resource or output dependencies; sequence those deployments instead.