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.
Install with Tessl CLI
npx tessl i github:pulumi/agent-skills --skill pulumi-automation-api64
Does it follow best practices?
If you maintain this skill, you can automatically optimize it using the tessl CLI to improve its score:
npx tessl skill review --optimize ./path/to/skillValidation for skill structure
Invoke this skill when:
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.
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)}`);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:
Embedded infrastructure:
Applications that provision their own infrastructure:
Replacing fragile scripts:
If you have Bash scripts or Makefiles stitching together multiple pulumi commands, Automation API provides:
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:
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:
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.
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 });
}
}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();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)}`);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;
}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}`) });
}));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.
Use onOutput callback for real-time feedback:
await stack.up({
onOutput: (message) => {
process.stdout.write(message);
// Or send to logging system, websocket, etc.
},
});| 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 |
b6b942f
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.