Embed Pulumi in a FastAPI Service with the Automation API
The Pulumi Automation API turns pulumi up into a library call, which means you can drive deployments from an HTTP service instead of a CLI. This guide — under Pulumi patterns and provider management — builds a small FastAPI endpoint that provisions a per-tenant stack on demand, the pattern behind self-service infrastructure portals.
Why This Matters
A CLI is fine for engineers, but product teams often need infrastructure created in response to an event: a new customer signs up, a preview environment is requested, a batch job needs a bucket. Wrapping programmatic deployments in an API lets those events trigger real, stateful deployments with full preview and rollback.
The key design constraint is that deployments are slow and stateful, so the HTTP layer must treat them as background work, not a synchronous request you block on.
Prerequisites
pulumi>=3.0andfastapi,uvicorninstalled in the service virtualenv- The Pulumi CLI on the host (the Automation API shells out to it) and a configured state backend
- Cloud credentials available to the service process via its execution role, not baked into the image
# CLI: the Automation API needs the pulumi binary on PATH
pulumi version && python -c "from pulumi.automation import create_or_select_stack"
Defining an Inline Program
The Automation API can run an inline program — a Python function instead of a project directory — which is ideal for embedding. Each tenant gets its own stack name so state stays isolated.
# service.py — a FastAPI endpoint that provisions a per-tenant bucket
# CLI: uvicorn service:app --port 8080
from fastapi import FastAPI, BackgroundTasks
from pulumi import automation as auto
import pulumi_aws as aws
app = FastAPI()
def _program(tenant: str):
def build():
bucket = aws.s3.BucketV2(f"{tenant}-data")
return {"bucket": bucket.bucket}
return build
def _deploy(tenant: str) -> None:
stack = auto.create_or_select_stack(
stack_name=f"tenant-{tenant}",
project_name="tenant-envs",
program=_program(tenant),
)
stack.workspace.install_plugin("aws", "v6.0.0") # Provider note: pin the plugin version
stack.set_config("aws:region", auto.ConfigValue("us-east-1"))
stack.up(on_output=print) # State implication: writes to the tenant's own state file
@app.post("/envs/{tenant}", status_code=202)
def create_env(tenant: str, bg: BackgroundTasks):
bg.add_task(_deploy, tenant)
return {"status": "accepted", "stack": f"tenant-{tenant}"}
Verification
Exercise the endpoint and confirm a stack and state file appear, then destroy it to prove the reverse path works.
# CLI: trigger a deployment and inspect the resulting stack
curl -X POST localhost:8080/envs/acme
pulumi stack ls # expect: tenant-acme listed
pulumi -s tenant-acme destroy --yes
Gotchas & Edge Cases
Concurrency corrupts state. Two overlapping up() calls on the same stack will collide on the state lock. Serialise per-stack work with a queue or a per-stack lock; never run two deployments of the same stack at once.
Long deployments outlive requests. A 30-second HTTP timeout will kill a deployment mid-flight if you run it synchronously. Always use background tasks or a worker queue and return a job id.
Plugin installs are slow. install_plugin downloads on first run; pre-bake plugins into the container image so cold starts do not time out.
FAQ
Can I preview instead of deploying from the API?
Yes — call stack.preview() and return the change summary. This is how self-service portals show a plan before a human approves the real up().
Where does state live for inline programs?
In whatever backend the workspace is configured for. Point it at S3 or Pulumi Cloud so multiple service replicas share state, as discussed in choosing a state backend.
Is the Automation API the same as the CLI?
It drives the same engine and state, so a stack created via the API is fully manageable from the CLI and vice versa.
Related
- Programmatic Deployments with the Pulumi Automation API — the core patterns for driving Pulumi from Python.
- Pulumi Automation API — the parent cluster on embedding Pulumi in applications.
- Structuring Pulumi Stacks per Environment — how per-tenant and per-environment stacks keep state isolated.