{"name":"pulumi-automation-api","url":"https://skills.sh/pulumi/agent-skills/pulumi-automation-api","install":"npx skills add pulumi/agent-skills --skill pulumi-automation-api","sdk":"pulumi","key":"pulumi/pulumi-automation-api","description":"Load this skill when a user asks how to run Pulumi programmatically, embed Pulumi in an application, orchestrate multiple stacks in code, build a self-service infrastructure portal, replace pulumi CLI shell scripts with code, or use the Pulumi Automation API (LocalWorkspace, createOrSelectStack, inline programs). Also load for questions about multi-stack sequencing, parallel deployments, or passing outputs between stacks via code.","hasContent":true,"content":"---\nname: pulumi-automation-api\nversion: 1.0.0\ndescription: Load this skill when a user asks how to run Pulumi programmatically, embed Pulumi in an application, orchestrate multiple stacks in code, build a self-service infrastructure portal, replace pulumi CLI shell scripts with code, or use the Pulumi Automation API (LocalWorkspace, createOrSelectStack, inline programs). Also load for questions about multi-stack sequencing, parallel deployments, or passing outputs between stacks via code.\n---\n\n# Pulumi Automation API\n\n## When to Use This Skill\n\nInvoke this skill when:\n\n- Orchestrating deployments across multiple Pulumi stacks\n- Embedding Pulumi operations in custom applications\n- Building self-service infrastructure platforms\n- Replacing fragile Bash/Makefile orchestration scripts\n- Creating custom CLIs for infrastructure management\n- Building web applications that provision infrastructure\n\n## What is Automation API\n\nAutomation 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.\n\n```typescript\nimport * as automation from \"@pulumi/pulumi/automation\";\n\n// Create or select a stack\nconst stack = await automation.LocalWorkspace.createOrSelectStack({\n    stackName: \"dev\",\n    projectName: \"my-project\",\n    program: async () => {\n        // Your Pulumi program here\n    },\n});\n\n// Run pulumi up programmatically\nconst upResult = await stack.up({ onOutput: console.log });\nconsole.log(`Update summary: ${JSON.stringify(upResult.summary)}`);\n```\n\n## When to Use Automation API\n\n### Good Use Cases\n\n**Multi-stack orchestration:**\n\nWhen you split infrastructure into multiple focused projects, Automation API helps offset the added complexity by orchestrating operations across stacks:\n\n```text\ninfrastructure → platform → application\n     ↓              ↓            ↓\n   (VPC)      (Kubernetes)   (Services)\n```\n\nAutomation API ensures correct sequencing without manual intervention.\n\n**Self-service platforms:**\n\nBuild internal tools where developers request infrastructure without learning Pulumi:\n\n- Web portals for environment provisioning\n- Slack bots that create/destroy resources\n- Custom CLIs tailored to your organization\n\n**Embedded infrastructure:**\n\nApplications that provision their own infrastructure:\n\n- SaaS platforms creating per-tenant resources\n- Testing frameworks spinning up test environments\n- CI/CD systems with dynamic infrastructure needs\n\n**Replacing fragile scripts:**\n\nIf you have Bash scripts or Makefiles stitching together multiple `pulumi` commands, Automation API provides:\n\n- Proper error handling\n- Type safety\n- Programmatic access to outputs\n\n### When NOT to Use\n\n- Single project with standard deployment needs\n- When you don't need programmatic control over operations\n\n## Architecture Choices\n\n### Local Source vs Inline Source\n\n**Local Source** - Pulumi program in separate files:\n\n```typescript\nconst stack = await automation.LocalWorkspace.createOrSelectStack({\n    stackName: \"dev\",\n    workDir: \"./infrastructure\",  // Points to existing Pulumi project\n});\n```\n\n**When to use:**\n\n- Different teams maintain orchestrator vs Pulumi programs\n- Pulumi programs already exist\n- Want independent version control and release cycles\n- Platform team orchestrating application team's infrastructure\n\n**Inline Source** - Pulumi program embedded in orchestrator:\n\n```typescript\nimport * as aws from \"@pulumi/aws\";\n\nconst stack = await automation.LocalWorkspace.createOrSelectStack({\n    stackName: \"dev\",\n    projectName: \"my-project\",\n    program: async () => {\n        const bucket = new aws.s3.Bucket(\"my-bucket\");\n        return { bucketName: bucket.id };\n    },\n});\n```\n\n**When to use:**\n\n- Single team owns everything\n- Tight coupling between orchestration and infrastructure is desired\n- Distributing as compiled binary (no source files needed)\n- Simpler deployment artifact\n\n### Language Independence\n\nThe Automation API program can use a different language than the Pulumi programs it orchestrates:\n\n```text\nOrchestrator (Go) → manages → Pulumi Program (TypeScript)\n```\n\nThis enables platform teams to use their preferred language while application teams use theirs.\n\n## Common Patterns\n\n### Multi-Stack Orchestration\n\nDeploy multiple stacks in dependency order:\n\n```typescript\nimport * as automation from \"@pulumi/pulumi/automation\";\n\nasync function deploy() {\n    const stacks = [\n        { name: \"infrastructure\", dir: \"./infra\" },\n        { name: \"platform\", dir: \"./platform\" },\n        { name: \"application\", dir: \"./app\" },\n    ];\n\n    for (const stackInfo of stacks) {\n        console.log(`Deploying ${stackInfo.name}...`);\n\n        const stack = await automation.LocalWorkspace.createOrSelectStack({\n            stackName: \"prod\",\n            workDir: stackInfo.dir,\n        });\n\n        await stack.up({ onOutput: console.log });\n        console.log(`${stackInfo.name} deployed successfully`);\n    }\n}\n\nasync function destroy() {\n    // Destroy in reverse order\n    const stacks = [\n        { name: \"application\", dir: \"./app\" },\n        { name: \"platform\", dir: \"./platform\" },\n        { name: \"infrastructure\", dir: \"./infra\" },\n    ];\n\n    for (const stackInfo of stacks) {\n        console.log(`Destroying ${stackInfo.name}...`);\n\n        const stack = await automation.LocalWorkspace.selectStack({\n            stackName: \"prod\",\n            workDir: stackInfo.dir,\n        });\n\n        await stack.destroy({ onOutput: console.log });\n    }\n}\n```\n\n### Passing Configuration\n\nSet stack configuration programmatically:\n\n```typescript\nconst stack = await automation.LocalWorkspace.createOrSelectStack({\n    stackName: \"dev\",\n    workDir: \"./infrastructure\",\n});\n\n// Set configuration values\nawait stack.setConfig(\"aws:region\", { value: \"us-west-2\" });\nawait stack.setConfig(\"dbPassword\", { value: \"secret\", secret: true });\n\n// Then deploy\nawait stack.up();\n```\n\n### Reading Outputs\n\nAccess stack outputs after deployment:\n\n```typescript\nconst upResult = await stack.up();\n\n// Get all outputs\nconst outputs = await stack.outputs();\nconsole.log(`VPC ID: ${outputs[\"vpcId\"].value}`);\n\n// Or from the up result\nconsole.log(`Outputs: ${JSON.stringify(upResult.outputs)}`);\n```\n\n### Error Handling\n\nHandle deployment failures gracefully:\n\n```typescript\ntry {\n    const result = await stack.up({ onOutput: console.log });\n\n    if (result.summary.result === \"failed\") {\n        console.error(\"Deployment failed\");\n        process.exit(1);\n    }\n} catch (error) {\n    console.error(`Deployment error: ${error}`);\n    throw error;\n}\n```\n\n### Parallel Stack Operations\n\nWhen stacks are independent, deploy in parallel:\n\n```typescript\nconst independentStacks = [\n    { name: \"service-a\", dir: \"./service-a\" },\n    { name: \"service-b\", dir: \"./service-b\" },\n    { name: \"service-c\", dir: \"./service-c\" },\n];\n\nawait Promise.all(independentStacks.map(async (stackInfo) => {\n    const stack = await automation.LocalWorkspace.createOrSelectStack({\n        stackName: \"prod\",\n        workDir: stackInfo.dir,\n    });\n    return stack.up({ onOutput: (msg) => console.log(`[${stackInfo.name}] ${msg}`) });\n}));\n```\n\n## Best Practices\n\n### Separate Configuration from Code\n\nExternalize configuration into files or environment variables:\n\n```typescript\nimport * as fs from \"fs\";\n\ninterface DeployConfig {\n    stacks: Array<{ name: string; dir: string; }>;\n    environment: string;\n}\n\nconst config: DeployConfig = JSON.parse(\n    fs.readFileSync(\"./deploy-config.json\", \"utf-8\")\n);\n\nfor (const stackInfo of config.stacks) {\n    const stack = await automation.LocalWorkspace.createOrSelectStack({\n        stackName: config.environment,\n        workDir: stackInfo.dir,\n    });\n    await stack.up();\n}\n```\n\nThis enables distributing compiled binaries without exposing source code.\n\n### Stream Output for Long Operations\n\nUse `onOutput` callback for real-time feedback:\n\n```typescript\nawait stack.up({\n    onOutput: (message) => {\n        process.stdout.write(message);\n        // Or send to logging system, websocket, etc.\n    },\n});\n```\n\n## Quick Reference\n\n| Scenario | Approach |\n| --- | --- |\n| Existing Pulumi projects | Local source with workDir |\n| New embedded infrastructure | Inline source with program function |\n| Different teams | Local source for independence |\n| Compiled binary distribution | Inline source or bundled local |\n| Multi-stack dependencies | Sequential deployment in order |\n| Independent stacks | Parallel deployment with Promise.all |\n\n## Related Skills\n\n- **pulumi-best-practices**: Code-level patterns for Pulumi programs\n\n## References\n\n- https://www.pulumi.com/docs/using-pulumi/automation-api/\n- https://www.pulumi.com/docs/using-pulumi/automation-api/concepts-terminology/\n- https://www.pulumi.com/blog/iac-recommended-practices-using-automation-api/\n","contentSource":"skills.sh/api/download/pulumi/agent-skills/pulumi-automation-api","contentFetchedAt":"2026-07-27T08:59:34.437Z"}