How to Integrate Pulumi with Your Headless CMS
Connect Pulumi to publish events so content changes can trigger infrastructure updates, preview environments, redirects, and deployment config without manual tickets.
What is Pulumi?
Pulumi is an infrastructure-as-code platform that lets teams define cloud resources with TypeScript, Python, Go, C#, Java, or YAML. Platform, DevOps, and product engineering teams use it to provision AWS, Azure, Google Cloud, Kubernetes, Cloudflare, and other services from versioned code. Pulumi sits in the same category as Terraform and cloud-native CDKs, with Pulumi Cloud adding state, policy, stack history, deployments, and access controls.
Why integrate Pulumi with a headless CMS?
Pulumi usually runs from code, pull requests, CI jobs, or Pulumi Cloud Deployments. That works well for infrastructure owned by engineers, but it gets awkward when content operations need to influence infrastructure. Think about a docs team adding a new product version that needs a CDN route, a marketing team publishing a regional microsite that needs a domain mapping, or a developer relations team launching a sandbox that needs a short-lived preview stack.,Connecting Pulumi to a headless CMS category tool gives you a controlled path from editorial change to infrastructure change. With Sanity, content is structured as typed JSON in the Content Lake, so a Pulumi program can read fields like `domains`, `redirects`, `regions`, `docsVersion`, and `stackName` directly. GROQ selects only the fields the infrastructure job needs, and webhooks fire when a document is published, updated, or deleted.,The alternative is usually a chain of tickets, copied JSON files, Slack approvals, and manual `pulumi up` commands. That can be fine for one site. It breaks down when you have 12 locales, 4 deployment environments, and release windows where content and infrastructure must move together. The trade-off is that you need guardrails. Donβt let arbitrary editors define raw infrastructure code. Model safe fields, validate them, and map them to Pulumi config in a small, reviewed integration layer.
Architecture overview
A typical setup starts with a schema in Sanity Studio for deployment-related content, such as site environments, docs versions, redirect rules, domains, or feature flags. When a document is published, a Sanity webhook sends a POST request to a Node.js webhook listener, or a Sanity Function runs server-side on the mutation. The handler reads the document ID from the event, fetches the full document from the Content Lake with GROQ, validates the fields, and passes the result to Pulumi. For long-running infrastructure updates, the webhook listener usually calls Pulumi Automation API from a service that has the Pulumi CLI, cloud credentials, and `PULUMI_ACCESS_TOKEN`. If your stack already uses Pulumi Cloud Deployment Settings, a Sanity Function can instead call the Pulumi Deployments REST endpoint, `POST /api/stacks/{org}/{project}/{stack}/deployments`, to start an update without running the Pulumi CLI itself. Pulumi then updates the target stack, such as Cloudflare routes, AWS CloudFront behaviors, Kubernetes config maps, or preview environments. End users see the result when the frontend requests the same structured content from Sanity and the infrastructure change has finished, for example a new docs version route returning the right page instead of a 404.
Common use cases
Content-driven domain and route updates
Publish a market or docs-version document in Sanity, then use Pulumi to update Cloudflare routes, CDN behaviors, or load balancer rules.
Preview stacks for launches
Create short-lived Pulumi stacks when a release or campaign document enters review, so editors can test real infrastructure before publish.
Redirects as reviewed content
Let editors maintain safe redirect fields in Sanity while Pulumi applies them to edge infrastructure as code.
Regional rollout config
Use Sanity fields for locales, regions, and rollout dates, then let Pulumi update deployment targets for each approved market.
Step-by-step integration
- 1
Set up Pulumi
Create a Pulumi account and organization, install the Pulumi CLI, run `pulumi login`, and create a project and stack such as `docs-infra/dev`. For a TypeScript webhook service, install `@pulumi/pulumi`, `@pulumi/pulumi/automation`, and the provider packages you use, such as `@pulumi/cloudflare` or `@pulumi/aws`. Store `PULUMI_ACCESS_TOKEN` and cloud provider credentials in your deployment environment, not in Sanity.
- 2
Model deployment content in Sanity Studio
Create a schema for the fields Pulumi is allowed to read, for example `stackName`, `domains`, `regions`, `redirects`, `docsVersion`, and `enabled`. Keep secrets out of the schema. If editors need to choose from secret-backed options, model an enum like `integrationProfile`, then resolve it to secrets in your webhook service.
- 3
Create a focused GROQ query
Write a GROQ query that fetches only the fields Pulumi needs. For example, query one site environment by document ID and project redirects into `{from, to, permanent}` objects. This keeps the sync payload small and avoids passing unrelated editorial fields into infrastructure code.
- 4
Create the sync mechanism
Use a Sanity webhook for publish, update, and delete events, with a projection that includes the document ID and type. Point it at a Node.js route, small API service, or queue worker. If the job only needs to trigger Pulumi Cloud Deployments, a Sanity Function can call the Pulumi REST API directly. If it needs Pulumi Automation API, run it in a service where the Pulumi CLI and cloud credentials are available.
- 5
Connect to Pulumi
Map validated Sanity fields to Pulumi stack config, then run `stack.up()` with Pulumi Automation API or start a Pulumi Cloud Deployment with the REST API. Keep the Pulumi program itself in Git, and treat Sanity content as input data, not executable infrastructure code.
- 6
Test the end-to-end path
Start with a dev stack. Publish a test document in Sanity Studio, inspect the webhook payload, confirm the GROQ result, check the Pulumi Cloud update log, and verify the frontend route, redirect, or preview environment. Add failure handling before production, including retry rules, signature verification, and a rollback plan for bad config.
Code example
import { createClient } from "@sanity/client";
import { LocalWorkspace } from "@pulumi/pulumi/automation";
import type { NextRequest } from "next/server";
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: "2025-02-19",
token: process.env.SANITY_READ_TOKEN!,
useCdn: false,
});
export async function POST(req: NextRequest) {
const { _id } = await req.json();
const site = await sanity.fetch(
`*[_id == $id][0]{
stackName,
domains,
"redirects": redirects[]{from, to, permanent}
}`,
{ id: _id }
);
if (!site?.stackName) {
return Response.json({ skipped: true }, { status: 202 });
}
const stack = await LocalWorkspace.selectStack({
stackName: site.stackName,
workDir: process.env.PULUMI_WORK_DIR!,
});
await stack.setConfig("site:domains", {
value: JSON.stringify(site.domains ?? []),
});
await stack.setConfig("site:redirects", {
value: JSON.stringify(site.redirects ?? []),
});
const result = await stack.up({ onOutput: console.log });
return Response.json({
stack: site.stackName,
changes: result.summary.resourceChanges,
});
}How Sanity + Pulumi works
Build your Pulumi integration on Sanity
Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect publish events with Pulumi infrastructure updates.
Start building free βCMS approaches to Pulumi
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Infrastructure-safe content modeling | Uses schema-as-code in Sanity Studio, so fields that feed Pulumi can be reviewed, versioned, and tested like application code. | |
| Triggering Pulumi on publish | Webhooks can fire on specific mutations, and Functions can run server-side logic when content changes. | |
| Field-level query control | GROQ can select only the fields Pulumi needs and join referenced documents in one query. | |
| Editor control with developer guardrails | Developers define the editorial interface, validation rules, and allowed fields, while editors work in Sanity Studio. | |
| Long-running infrastructure updates | Webhooks can call a dedicated worker for Pulumi Automation API, while Functions can trigger Pulumi Cloud Deployments for lighter jobs. | |
| Multi-channel reuse | The same structured content can feed Pulumi, websites, mobile apps, and AI agents through APIs like GROQ and Agent Context. |
Keep building
Explore related integrations to complete your content stack.
Sanity + GitHub
Connect content changes with pull requests, GitHub Actions, and repository workflows that sit next to your Pulumi code.
Sanity + GitLab
Use Sanity publish events with GitLab CI/CD for teams that run Pulumi updates from GitLab pipelines.
Sanity + Storybook
Pair structured content with component previews so teams can test content-driven UI before Pulumi promotes the environment.