Pulumi Automation API: Infrastructure as a Real Program
The CLI is fine for humans. When you need to provision infra from your own app or platform, the Pulumi Automation API turns deployments into function calls.
- #iac
- #pulumi
- #automation-api
- #platform-engineering
- #self-service
- #golang
Most people meet Pulumi through its CLI: write a program, run pulumi up, watch resources appear. That’s great for an engineer at a terminal. It falls apart the moment you want infrastructure provisioned by software — a self-service portal, a CI pipeline that needs richer control than shelling out, or a SaaS product that spins up a per-tenant stack on signup.
The Pulumi Automation API is the answer. It exposes the entire deployment engine as a library, so pulumi up becomes a function you call from your own code. No CLI subprocess, no parsing stdout, no brittle shell glue. After building a couple of internal platforms on it, here’s what I wish I’d known at the start.
What the Automation API actually is
The Automation API is the Pulumi engine — the thing that does the diffing, ordering, and resource lifecycle — available as a programmatic interface in Go, Python, TypeScript, and .NET. You drive stacks (select, configure, preview, up, destroy, get outputs) entirely in code, with structured results instead of scraped text.
There are two ways to feed it a program:
- Inline programs — the Pulumi program is a function in the same process. No separate project directory, no
Pulumi.yaml. Cleanest for embedding in an app. - Local programs — you point the API at an existing on-disk Pulumi project. Useful for wrapping infra you already have.
A self-service provisioning endpoint
Here’s the pattern that justifies the whole API: an HTTP handler that provisions a stack on demand. This Go example creates an S3 bucket per request using an inline program.
func provisionHandler(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
stackName := r.URL.Query().Get("tenant")
// Inline program — the infra definition lives right here
program := func(pCtx *pulumi.Context) error {
bucket, err := s3.NewBucket(pCtx, "tenant-bucket", &s3.BucketArgs{
Tags: pulumi.StringMap{"tenant": pulumi.String(stackName)},
})
if err != nil {
return err
}
pCtx.Export("bucketName", bucket.ID())
return nil
}
stack, err := auto.UpsertStackInlineSource(
ctx, stackName, "tenant-provisioner", program)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// Configure, then deploy
stack.SetConfig(ctx, "aws:region", auto.ConfigValue{Value: "us-east-1"})
res, err := stack.Up(ctx, optup.ProgressStreams(os.Stdout))
if err != nil {
http.Error(w, err.Error(), 500)
return
}
fmt.Fprintf(w, "bucket: %s\n", res.Outputs["bucketName"].Value)
}
Each tenant gets an isolated, independently-managed stack, created by an API call. Destroying it later is stack.Destroy(ctx). This is the foundation of per-customer infrastructure, ephemeral preview environments, and internal developer platforms.
State and backends matter more here
The CLI hides state management behind defaults. When you’re running deployments from a long-lived service, be deliberate:
// Use an explicit, shared backend so concurrent provisions
// don't collide and state survives a pod restart.
ws, err := auto.NewLocalWorkspace(ctx,
auto.Project(workspace.Project{
Name: "tenant-provisioner",
Runtime: workspace.NewProjectRuntimeInfo("go", nil),
Backend: &workspace.ProjectBackend{
URL: "s3://my-pulumi-state-bucket",
},
}),
)
Point at S3, Azure Blob, GCS, or Pulumi Cloud — never the local filesystem for a service that scales horizontally. Each stack gets its own state file under that backend, and Pulumi’s locking prevents two concurrent up calls on the same stack from corrupting state.
Concurrency, the part that bites
The engine serializes operations per stack, but your service will run many stacks at once. Two rules keep you out of trouble:
- Never run two operations on the same stack concurrently. Serialize per-stack in your app — a per-tenant mutex or a work queue keyed by stack name.
- Cap total parallelism. A hundred simultaneous
upcalls will exhaust your cloud API rate limits and your memory. A bounded worker pool is mandatory.
Streaming output and structured results
Because you’re not parsing CLI text, you get real data back. res.Summary tells you resource counts and the result kind; res.Outputs is a typed map. Stream progress to logs or a websocket with optup.ProgressStreams, and surface a clean status to the user instead of raw engine chatter. This is the ergonomic payoff over shelling out to the CLI.
Where AI fits
The Automation API is verbose — lots of context plumbing, option structs, and error handling. I use an assistant to scaffold the handler shells, generate the inline program for a given resource shape, and draft the worker-pool/concurrency wrapper, then I review the state-backend and locking decisions by hand because those are the parts that hurt if they’re wrong. Keep a few Pulumi prompts around for generating inline programs from a plain-English resource description.
When to reach for it (and when not to)
Use the Automation API when software needs to provision infra: self-service portals, ephemeral environments per PR, per-tenant stacks, or a platform API. Do not reach for it to replace a normal pulumi up that a human runs — that’s added complexity for no benefit. The CLI is the right tool for human-driven deployments; the Automation API is the right tool when the “human” is your own program.
The mental shift is the valuable part: infrastructure stops being a thing you run and becomes a thing you call. Once provisioning is a function, you can wrap it in auth, quotas, approval flows, and billing — and you’ve built a platform. For the broader landscape, see our Infrastructure as Code guides.
Automation API code and generated programs are assistive, not authoritative. Test provisioning and teardown against a sandbox account before exposing any self-service endpoint.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.