Skip to content

Runbook: web deployment topology

Reference for every web property Celestial Intelligence deploys — where it’s hosted, how the dev→prod deploy model works, and how the marketing site stays hidden behind a placeholder until launch.

This is a reference runbook (not a step-by-step). Read the relevant section before deploying; steps marked ⚠ change what the public sees.

PropertyPackageCF Pages projectDomainPipelines
Marketing sitepackages/marketing-sitecelestial-marketingcelestialintelligence.co, www.marketing-dev, marketing-promote, marketing-maintenance
Docsdocs/ (docs-site)celestial-docsdocs.celestialintelligence.codocs-dev, docs-promote
Lens (design gallery)packages/lenscelestial-lens (-9p1 suffix)lens.celestialintelligence.colens-dev, lens-promote
Schema hostpackages/schema-api etc.celestial-schemasschema.celestialintelligence.comanual
Dashboardpackages/dashboard-server— (Hetzner 5.161.83.131, systemd)dashboard.celestialintelligence.co (planned)deploy-dashboard

All Pages properties are static-asset deploys via the cloudflare-pages artifact target. Lens, docs, and marketing each declare a module (*.ssmod.yaml), composed into platform.prod.ssws.yaml and platform.dev.ssws.yaml.

The deploy model — dev on push, gated promote

Section titled “The deploy model — dev on push, gated promote”

Each web property has two pipelines, broken out per module so they ship independently — never all-or-nothing:

  • <property>-dev — triggered on every push to main (path-filtered to that property + its dependencies). Deploys --env=dev → a preview at dev.<project>.pages.dev. Safe and ungated: a dev deploy is never publicly visible (custom domains stay bound to the production deploy).
  • <property>-promotemanual only. Builds the current commit, pauses on a durable human-approval wait step, then deploys --env=prod. This is the only path to production.

So: pushing main refreshes the dev previews; production is always a deliberate, human-approved *-promote run. Pushing never changes what the public sees.

PipelineDeploysTrigger
marketing-devMarketing → dev previewpush→main, paths packages/marketing-site/**, packages/components/**, packages/theme/**; + manual
marketing-promoteMarketing → productionmanual (human-gated)
marketing-maintenanceToggle the coming-soon placeholdermanual (mode=on / mode=off)
docs-devDocs → dev previewpush→main, paths docs/src/**, docs/public/**, docs/astro.config.mjs, docs/package.json; + manual
docs-promoteDocs → productionmanual (human-gated)
lens-devLens → dev previewpush→main, paths packages/lens/**, packages/components/**, packages/theme/**; + manual
lens-promoteLens → productionmanual (human-gated)
deploy-scribeScribe services → productionmanual (human-gated)
deploy-starsystemStarsystem agent → productionmanual (human-gated)
deploy-starflowStarflow server → productionmanual (human-gated)
deploy-dashboardDashboard server → productionmanual (human-gated)
deploy-migrationsDB migrations → productionmanual (human-gated)

All web pipelines deploy via ss artifact deploy — the cloudflare-pages target creates the Pages project and binds custom domains, so no wrangler / CF-credential / provisioning machinery is needed.

The backend tier (deploy-scribe/-starsystem/-starflow/-dashboard/ -migrations) replaced the old monolithic deploy-prod — each module deploys on its own, never all-or-nothing. It’s manual-only by design: backend services have no dev-preview equivalent, and a deploy restarts a live Hetzner service, so the timing should be deliberate. Each runs starsystem deploy scoped to that module’s services via the starsystem-deploy step’s services: selector. (Spacecraft and the ss/sf CLIs aren’t here — they ship as binaries via release-mac, not as deployed services.)

One Cloudflare zone, celestialintelligence.co, owned entirely by the infra-domains.tooling-co-dns dns-zone service. Records: @ + wwwcelestial-marketing, schemacelestial-schemas, lenscelestial-lens-9p1, docscelestial-docs. The registrar is Porkbun; NS is delegated to Cloudflare (ADR-025). Seven brand-defense domains are registered at Porkbun and URL-forward to .co — no CF zone.

Apply DNS changes with:

ss deploy --service=infra-domains.tooling-co-dns --env=prod \
--workspace-file=platform.prod.ssws.yaml

marketing-site.ssmod.yaml declares two services on the same celestial-marketing Pages project:

  • marketing-site.placeholder — the static placeholder/ dir (a “coming soon” page, noindex).
  • marketing-site.site — the real built site.

Whichever was last deployed with --env=prod is what the public domain serves. It’s a deploy swap — no env flag, no code toggle.

  • Public domain currently serves: placeholder (pre-launch).
  • The real site is continuously refreshed on dev.celestial-marketing.pages.dev by marketing-dev on every push.

To launch — publish the real site for the first time:

sf run marketing-promote # builds, waits for your approval, deploys prod

To hide / restore the live site (post-launch incidents — “take it down for a sec”), the marketing-maintenance pipeline is a toggle:

sf run marketing-maintenance --input mode=on # ⚠ hide behind the placeholder
sf run marketing-maintenance --input mode=off # restore the live site

mode=on is fast — the placeholder is static, no build. Pre-launch the site simply sits in the mode=on state (placeholder), and the first marketing-promote is the launch.

--env=dev deploys to the Pages project’s dev branch — a preview at dev.<project>.pages.dev. Custom domains stay bound to the production deployment, so a dev deploy is never publicly visible. --env=prod deploys to the production branch and binds the custom domains.

The pipelines are the normal path, but any property can be deployed by hand. Build, then ss artifact deploy:

npm run build -w packages/lens # → packages/lens/dist
npm run build -w docs-site # → docs/dist
npm run build -w packages/marketing-site # → packages/marketing-site/dist
node packages/starsystem-cli/dist/cli.js artifact deploy lens.site \
--env=dev --workspace-file=platform.dev.ssws.yaml # or --env=prod / platform.prod

ss artifact deploy does not require a git push — it ships the local dist/. Pushing main is what triggers the *-dev pipelines.

The marketing site and Lens are static (no framework). UI is built from @celestial/components — the cel-* Lit web components — which both import, alongside @celestial/theme for the design tokens. Lens is the gallery for those components; the marketing site composes them into a static index.html. Neither ships React. A change under packages/components/** therefore refreshes both the Lens and marketing dev previews.