Make self-hosted Pulumi Cloud a first-class story - #20914
Conversation
Groundwork for making self-hosted Pulumi Cloud a first-class story. This is the subset that needed no design, legal, or engineering input. The /security/ "Read Whitepaper" button has been dead since January 2026. Commit 30aa059 converted the whitepaper from a PDF page resource into a markdown content page and adjusted the .Resources.GetMatch call by dropping the .pdf suffix, but GetMatch only ever matches page resources -- content pages are not resources of their branch bundle, so the lookup returned nil and the button rendered href="". Switching to site.GetPage keeps the link resilient to a future rename, and the else branch errorf's rather than silently emitting nothing, since silent nil is what let this rot unnoticed for seven months. /security/ was also absent from the site header entirely. It now sits in the Product dropdown under a new "Security & trust" heading, which is deliberately named to accommodate the self-hosted entry that lands once that page is rebuilt -- self-hosting is a data-control decision before it is a deployment one, so the two belong together. The enterprise page advertised "VMWare vSphere" as a deployment option. No vSphere installer exists, and the tile behind that claim only ever pointed at a contact form, so the copy now names the six installations we actually ship. It also offered a "Request Free Trial" for self-hosting, which sets up a bad first interaction: there is no free trial, only a guided proof of concept gated at Business Critical. Spear AI moves from ai-ml to public-sector. The customer is the US Navy and the story is ATO, GovCloud, and air-gapped Secret Cloud; filing it under ai-ml left our one genuine defense reference invisible to anyone browsing public sector, a term page that had exactly one entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Revives the unmerged self-hosted-first-class branch from June 17 (e8405d5), rebased onto current master and reframed. Three pieces were worth keeping: the install front door, the Docker Compose quickstart rewrite, and the Terraform Enterprise migration guide. The original premise was self-serve -- "deploy it yourself," with the guided proof of concept demoted to a fallback. That premise depended on an evaluation license a user could obtain without a sales call, which the commit message itself flagged as not yet existing. It still doesn't, so a reader was told they could try it themselves and then hit a sales gate. The copy now says plainly that a license key comes from a proof of concept, while keeping every mechanical step documented, so when a self-serve license does ship only the license-acquisition sentence changes. The quickstart carried two errors forward from the page it replaced, both verified against quickstart-docker-compose/scripts/run-ee.sh: the script reads PULUMI_LOCAL_DATABASE_HOST, not PULUMI_LOCAL_DATABASE_NAME, and it creates a Docker network named pulumi-self-hosted-installers, not pulumi-ee. The bring-your-own-database section also told readers to ask sales for the migrations folder; the compose file pulls pulumi/migrations from Docker Hub. The Terraform Enterprise guide claimed air-gapped operation covers "environments that require FedRAMP." Pulumi holds no FedRAMP authorization, and self-hosting does not change that -- it changes who owns the boundary, so the bullet now says that instead. Self-hosting joins the header nav under the Security & trust heading added for it, alongside Security & compliance, because self-hosting is a data-control decision before it is a deployment one. Two page-level meta_image overrides were dropped: AGENTS.md discourages them for docs pages, where the build generates an on-brand card from the title. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every fix here was verified against pulumi-self-hosted-installers rather than
reasoned about, because the errors all share a cause: prose that was written
once and never rechecked against the code it describes.
The console page told readers "the container image repository is private.
Contact us if you would like to evaluate." Both images are public --
pulumi/service reports 220,518 pulls and pulumi/console 114,034 against the
Docker Hub API -- so that sentence was turning away self-evaluation for no
reason.
The API page repeated the PULUMI_LOCAL_DATABASE_NAME error corrected on the
quickstart page; run-ee.sh reads PULUMI_LOCAL_DATABASE_HOST.
The EKS page documented Kubernetes 1.18 through 1.21 and an
`npm run install -- update --` command. Both describe eks-hosted-deprecated,
whose README now opens with DEPRECATED and whose revision table stops at K8s
1.24 in February 2024. The current installer is v4.0 and the version is set
through `clusterVersion` in the 05-eks-cluster project. Note an upstream
inconsistency worth chasing: that project's Pulumi.README.yaml still ships
clusterVersion "1.30" while the installer README's revision history claims
1.34.0 for v4.0, so the table here follows the revision history and the page
deliberately states no default.
The air-gapped guide told readers to deploy with Helm. No chart exists in the
installers repo or anywhere else, so it now points at the bring-your-own-infra
installer, which is what actually applies manifests to an existing cluster. It
also linked to /docs/administration/self-hosting/pulumi-cloud, an alias of the
section index -- a self-referential link.
The operations database page presented db.r5.large as "the self-hosted
installers default," which is true only for EKS; the ECS installer defaults to
db.t3.small, a burstable instance nobody should reach production on unknowingly.
The table is now per-installer.
Finally, the nine pages that were missing the edition callout now carry
{{< self-hosting-trial-note />}}, so every self-hosting page states how to get
a license instead of only some of them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every modern installer README opens with a hard stop: do not point this installer at an existing install, contact support to migrate the database first. That warning existed nowhere in the docs. The only trace was two rows in the API component's environment-variable table, on a 510-line reference page nobody reads before running `pulumi config set imageTag`. The gate now lives in a shortcode and appears on the upgrades guide and in the update section of all six production deployment pages -- the exact places a reader is about to change an image tag and run `pulumi up`. Stating it once and including it keeps the six copies from drifting the way the original warning did. It supports .Inner for the same reason self-hosting-trial-note does: Hugo only accepts the self-closing form for shortcodes that evaluate it, and matching the sibling keeps both usable the same way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page was a bespoke 2022 layout that predated the section system every other product page uses. It called the platform a "Cloud Engineering Platform", offered "Azure Active Directory" as an identity option, omitted ECS entirely though it is the most complete installer we ship, and routed every docs link through the retired /docs/pulumi-cloud/self-hosted/ path so each click was a redirect. Moving it to type: page + layout: template-page deletes the one-off layout and keeps the URL. The identity copy was wrong on substance, not just naming: self-hosted has no Azure AD OAuth connector. Entra ID reaches it through SAML, which is what the page now says. Deployment targets are Phosphor icons rather than cloud logos. Every logo the old page used lives only under static/, and template partials resolve images through assets/fingerprinted/, where a missing file panics the build. Icons avoid copying six assets to say something an icon says just as well. The VMware tile is gone. It linked to a contact form with no documentation behind it and implied a vSphere installer that does not exist. A "Something else?" card does the same job honestly -- the images run on any OCI-compatible orchestrator -- and eight cards fill two rows of four cleanly. Compliance gets a three-column section where the third column is headed "No FedRAMP authorization" and carries the same weight as the SOC 2 column. A security reviewer who can see immediately what we do not hold will trust what we do claim; burying the non-claim in a footnote produces the opposite. All three columns get a CTA so the negative one does not read as a dead end. FIPS is deliberately absent: nothing in this repo, pulumi/pulumi, or the installers substantiates a claim about our own use of it. Rendering a HubSpot form needed a new section type, since none existed. The #self-hosted-trial anchor is a contract, not decoration -- the self-hosting-trial-note shortcode, the pricing FAQ, and the enterprise page all deep-link to it -- so the partial documents that. The meta image is generated with the site's own Satori renderer using the same "title" template as /what-is/ and /migrate/, because generate-meta-images.mjs does not cover product/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page claimed one thing -- SOC 2 Type II -- in a single sentence, and the 527-line whitepaper behind it names no certifications at all. That combination undersells a real posture and leaves a security reviewer with nowhere to land, so they email an account rep instead. The most useful sentence on a trust page is the negative one. Stating plainly that we do not hold ISO 27001, HITRUST, or PCI DSS lets a reviewer stop looking rather than assume we are hiding it, and it makes the SOC 2 claim more credible, not less. The same logic drives the FedRAMP section: no authorization, no ATO, no Marketplace listing, said outright, followed by what self-hosting actually changes -- who owns the boundary. Two distinctions the old page blurred. Our pre-built CIS/NIST/PCI policy packs help customers assess their own infrastructure; they say nothing about Pulumi's certification status, and a procurement reader could easily conflate the two. And per our own /what-is/what-is-soc-2/, SOC 2 is an attestation rather than a certification -- worth being precise about here even though the marketing shorthand elsewhere is fine. The whitepaper now gets a paragraph describing what is actually in it, drawn from its real section headings, so the strongest asset we have is no longer sitting behind a bare link. /trust/ and /trust-center/ alias here rather than becoming a second page. Whichever surface a reviewer guesses, they land on the authoritative one, and there is no seam for the two to drift across -- which is how the whitepaper and this page diverged in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every menu identifier and parent in the tree still read administration-security-compliance-self-hosted-*, left over from a prior move out of a Security & Compliance section that no longer contains these pages. The names were functional but actively misleading to anyone editing frontmatter here, since the section they name is a sibling. Identifiers are not URLs and nothing outside this content directory references them -- verified across templates, data files, SCSS, and scripts before renaming -- so this is a pure legibility fix. The rendered nav tree is unchanged: all 29 entries still resolve under Self-Hosting. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two pages built entirely from facts that already existed but were scattered. System requirements collects what an installation needs -- licenses, compute, MySQL, object storage, OpenSearch, Kubernetes, DNS, TLS, network -- from four component pages, the operations guide, and the installer READMEs. Three of those facts were only ever documented upstream and appeared nowhere on the site: the `pulumi` database must exist before you install, the application user needs two specific GRANTs, and the MySQL server must accept inbound ICMP or the installer's connectivity check fails. Troubleshooting exists mostly for its first entry. The error string `ALGORITHM=INPLACE is not supported` is what a failed migration actually prints, and until now it appeared only inside the operations database page, where nobody searching that text would land. The other eight entries have the same property: each is a failure someone hits during a real install, indexed by the symptom rather than by the subsystem that causes it. Not done here, and worth a decision: the api, console, and search component pages each still carry their own minimum-requirements table. Collapsing them into this page is the obvious follow-up, except console's table says 1 CPU core and 1 GB while operations/compute-sizing says 0.5 vCPU and 512 Mi. Those may be measuring different things -- host minimum versus container request -- so they need an answer rather than a guess before anything gets deleted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Self-hosting had no entry at the docs root at all -- the only pointer was the word "self-hosting" inside the Administration card's description -- and on the Administration index it was the fifth card in a block headed "Getting started", which is not what it is. It now has its own section there and its own card at the root. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The API reference had grown to 510 lines, and 147 of them -- nearly a third -- were an operator runbook rather than container reference: collector configuration, the metrics endpoint, and two full Prometheus setups including sixty lines of collector YAML. That material belongs next to the monitoring guide, which until now told you what to alert on without telling you how to get metrics out of the service in the first place. The four environment variables stay in the API reference, under the same #opentelemetry anchor, so the variable reference remains complete in one place and the inbound link from security-hardening and the installer READMEs keeps working. Only monitoring.md needed repointing. Deliberately not splitting further. This is a reference, and references are more useful when one search finds everything; sharding it by storage backend or identity provider would multiply cross-links and force anyone comparing two options to open two pages. The remaining ~370 lines all answer the same question -- what do I set on the API container for this subsystem. Unrelated and pre-existing, noted while checking anchors: the 2024 post self-hosted-search-and-deploy links to components/api#deployments, but that heading has always anchored as #pulumi-deployments. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The page described what the installer creates and then sent the reader to a README on GitHub for every actual step. It now carries the steps: prerequisites split by what you must supply, the three projects in deployment order with real commands, a configuration reference as tables rather than a wall of `pulumi config set` lines, and a verification sequence. Verification is the part that was missing everywhere and is worth stating plainly: hit /api/status, `pulumi login`, `pulumi whoami`, then a throwaway `pulumi new` / `up` / `destroy`. That sequence is what the quickstart's own Go integration tests do, so it is a real check rather than a plausible-looking one. Two facts that only existed upstream now appear here. The ECS installer defaults `dbInstanceType` to db.t3.small, a burstable instance that should not carry production traffic unnoticed, so defaults are called out under Sizing rather than left implicit. And hiding email login in the console does not disable the API handler behind it -- a reader configuring SSO would reasonably assume it does. The first-signup-becomes-administrator warning is now a shortcode. It applies to all seven deployment options but was documented only in the AKS installer's README, and it is the kind of thing you can only fix once. This page is the pattern for the remaining six; the others still hand off to their READMEs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Whoever signs up first on a fresh installation becomes the administrator, and turning on SAML does not close email and password signup on its own. That is true of all seven deployment options and was documented in exactly one place: the AKS installer's README on GitHub. It is also unrecoverable in the way that matters -- by the time you notice, someone else owns the organization -- so it belongs next to the install steps rather than in a security page nobody reads first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@claude please review this PR. |
There was a problem hiding this comment.
✅ No issues found
About Unblocked
Unblocked has been set up to automatically review your team's pull requests to identify genuine bugs and issues.
📖 Documentation — Learn more in our docs.
💬 Ask questions — Mention @unblocked to request a review or summary, or ask follow-up questions.
👍 Give feedback — React to comments with 👍 or 👎 to help us improve.
⚙️ Customize — Adjust settings in your preferences.
|
Claude finished @joeduffy's task in 6m 49s —— View job Review: Make self-hosted Pulumi Cloud a first-class story
This is strong work, and the parts of it I could verify independently hold up. The problem is concentrated in one place: the new ECS config reference tracks Blocking — the ECS install sequence does not work as writtenAll line references are 1. const route53ZoneName = stackConfig.require("domainName");
const route53Subdomain = stackConfig.get("subDomain") || "";
2. const recaptchaSiteKey = stackConfig.require("recaptchaSiteKey");
const recaptchaSecretKey = stackConfig.requireSecret("recaptchaSecretKey");Neither appears in the prerequisites or the config table, so a reader following the page hits a hard failure with nothing to tell them what to obtain. (The EKS 3.1 changelog mentions "move to Turnstile for recaptcha," so it's worth asking engineering what value belongs here for a self-hosted install.) 3. 4. The OpenSearch rows are wrong on name, case, and requiredness. Actual keys are 5. 6. Step 3's command block can't succeed on its own. It omits every The task CPU/memory defaults you documented (API 512/1024, console 256/512) are correct — Given all six trace to the README rather than the code, it may be worth filing an issue against Claims I'd want engineering to confirm
"The same capabilities as the SaaS." OpenSearch as a hard requirement. The product page says an installation is "backed by a MySQL 8.0 database, object storage, and an OpenSearch cluster you provide," but your own new Smaller things
What I couldn't checkThis environment has no |
Lighthouse Performance ReportCommit: 2c73a39 | Metric definitions
|
There was a problem hiding this comment.
🧹 Optional style suggestions from the pre-merge review — apply or dismiss; none of them block.
Generated by Claude Code
| To run against an existing MySQL 8.0 instance instead of the bundled database, start the service with the base `docker-compose.yml` and point `PULUMI_LOCAL_DATABASE_HOST` and `PULUMI_LOCAL_DATABASE_PORT` at it. The script creates and uses a Docker network named `pulumi-self-hosted-installers`, so the database must be reachable from that network. | ||
|
|
||
| ### Updating the Pulumi Cloud Containers | ||
| The schema is applied by the `pulumi/migrations` container, which the compose file pulls from Docker Hub — you do not need to obtain migration scripts separately. |
There was a problem hiding this comment.
[style] wordiness — plainer verb, same meaning
| The schema is applied by the `pulumi/migrations` container, which the compose file pulls from Docker Hub — you do not need to obtain migration scripts separately. | |
| The schema is applied by the `pulumi/migrations` container, which the compose file pulls from Docker Hub — you do not need to download migration scripts separately. |
|
|
||
| ## AKS-Hosted System Management and Maintenance | ||
|
|
||
| ### Pulumi Cloud Updates |
There was a problem hiding this comment.
[style] heading capitalization — H2+ headings use sentence case
| ### Pulumi Cloud Updates | |
| ### Pulumi Cloud updates |
|
|
||
| ## BYO Infrastructure Hosted System Management and Maintenance | ||
|
|
||
| ### Pulumi Cloud Updates |
There was a problem hiding this comment.
[style] heading capitalization — H2+ headings use sentence case
| ### Pulumi Cloud Updates | |
| ### Pulumi Cloud updates |
|
|
||
| ## GKE-Hosted System Management and Maintenance | ||
|
|
||
| ### Pulumi Cloud Updates |
There was a problem hiding this comment.
[style] heading capitalization — H2+ headings use sentence case
| ### Pulumi Cloud Updates | |
| ### Pulumi Cloud updates |
|
|
||
| ## Local-Docker System Management and Maintenance | ||
|
|
||
| ### Pulumi Cloud Updates |
There was a problem hiding this comment.
[style] heading capitalization — H2+ headings use sentence case
| ### Pulumi Cloud Updates | |
| ### Pulumi Cloud updates |
Pre-merge Review — Last updated 2026-08-17T19:30:10ZTip Summary: This PR promotes self-hosted Pulumi Cloud to a first-class story: it renames the whole Review confidence:
Investigation log
✏️ 5 one-click style suggestions are posted inline — apply them from the Files changed tab, individually or with Add suggestion to batch. 🔍 Verification trail387 claims extracted · 287 verified · 38 unverifiable · 16 contradicted · 4 framing-drift · 1 detector finding
Important Please don't hide, resolve, or delete this comment! It breaks things! 📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches. |
continued from previous comment
Important Please don't hide, resolve, or delete this comment! It breaks things! 📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches. |
continued from previous comment
Important Please don't hide, resolve, or delete this comment! It breaks things! 📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches. |
continued from previous comment
Important Please don't hide, resolve, or delete this comment! It breaks things! 📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches. |
🚨 Outstanding in this PRThese must be resolved or refuted before merging.
|
Self-hosted Pulumi Cloud is a real product with seven supported installations, and the website has been hiding it behind a marketing page written in 2022, a navigation entry that didn't exist, and a compliance page that claimed less than we can actually defend. This fixes the surface around it and corrects a dozen factual errors in the docs.
Marketing and trust
/product/self-hosted/rebuilt on the template-partials system every other product page uses; the bespoke 146-line layout is deleted, URL unchanged. The old page said "Cloud Engineering Platform", offered "Azure Active Directory" as an identity option, omitted AWS ECS entirely despite it being our most complete installer, and routed every docs link through the retired/docs/pulumi-cloud/self-hosted/path so each click was a redirect./security/now states what we don't hold (ISO 27001, HITRUST, PCI DSS) alongside the SOC 2 Type II attestation, and separates our compliance policy packs from our own certification status — a distinction a procurement reader could easily conflate./trust/and/trust-center/alias here.pulumi/pulumi, orpulumi-self-hosted-installerssubstantiates one. It can land later once engineering documents precisely what's true./security/"Read Whitepaper" button has been dead since January 2026 —.Resources.GetMatchon a branch bundle never matches a content page, so it renderedhref="". Fixed withsite.GetPageand anerrorfguard; I proved the guard fires by deleting the whitepaper and watching the build exit 1.Docs corrections
Every fix verified against installer source, not reasoned about:
PULUMI_DATA_PATHdefaults to/tmp/pulumi-ee/data${HOME}/pulumi-self-hosted-installers/dataPULUMI_LOCAL_DATABASE_NAMErun-ee.shreadsPULUMI_LOCAL_DATABASE_HOSTpulumi-eepulumi-self-hosted-installerspulumi/migrationsfrom Docker Hubpulumi/serviceis public with 220,518 pulls;pulumi/consolewith 114,034npm run install -- update --eks-hosted-deprecated; current installer is v4.0db.r5.largeis "the installers default"db.t3.smallPULUMI_LICENSE_KEYcauses a hardexit 1and was never mentioned — the most common first-run failure.Two safety gaps
New and restructured
system-requirementsandtroubleshootingpages, both built from facts that already existed but were scattered. Troubleshooting is indexed by symptom — the stringALGORITHM=INPLACE is not supportedis what a failed migration actually prints, and it previously lived only inside the operations database page.api.md(510 → 376 lines), keeping the env vars and the#opentelemetryanchor in place so inbound links — including from the installer READMEs, which we don't control — keep working.self-hosted-first-classbranch: its install front door, quickstart rewrite, and Terraform Enterprise migration guide. Its self-serve framing is rewritten, because the evaluation-license mechanism it assumed still doesn't exist — readers were told they could try it themselves and then hit a sales gate.Verification
make lintandmake buildclean. No broken links introduced. Rendered output checked for every claim above.Known follow-ups
components/console.mdsays 1 CPU / 1 GB whileoperations/compute-sizing.mdsays 0.5 vCPU / 512 Mi. They may be measuring different things (host minimum vs. container request), so I left both rather than delete one on a guess. This needs an answer before the duplicate requirements tables can be collapsed./guides/404s from both the header nav and footer; the JSON-LD FAQ schema emits raw shortcode markup intoacceptedAnswer.text; the 2024 postself-hosted-search-and-deploylinks tocomponents/api#deployments, which has always anchored as#pulumi-deployments.🤖 Generated with Claude Code