Skip to content

How publishing and the site fleet work

The problem: one platform publishes to many sites, each with its own brand, domain and theme. Coupling the content engine to site rendering makes every theme change a platform deploy and every platform bug a fleet outage. The design answer is three layers that deploy independently.

publish payloadrender to Hugo contentbuild + deployverified live URLPlatformgenerates + gates articlesPublishing hubseparate microservice:instance registry ·renderers · buildersPer-brand site templatesshared theme + brandoverridesFleet of static sitesserved by nginx

The hub is a vendored microservice with its own instance registry: per-site records carrying template, status, deploy target and public URL. The registry — not the platform database — is the hub’s source of truth for where each brand lives. The platform hands the hub a finished article; everything from “markdown file in the right folder” to “site rebuilt and rsynced” is the hub’s business.

Site hostBuilderHugo adapterHub APIPlatformSite hostBuilderHugo adapterHub APIPlatformpublish-live (article payload)upsert by source_url(article registry — republish ≠ duplicate)build site (hugo --minify)deploy (rsync to web root)fetch the live URL200 → publication counts

Two details carry the reliability:

  • Upsert by source URL. The hub keeps its own article registry keyed by the source URL, so republishing an improved version replaces the page instead of minting a duplicate.
  • Slug identity lives on the platform side. The published-URL registry keys identity as (instance, normalized source URL) — deliberately not the article’s internal id, which changes on regeneration. A regenerated article reuses its frozen slug; a brand-new source mints one. This is what protects accumulated SEO equity from being reshuffled by routine regeneration.

Layer 3 — brand configuration is code, not server state

Section titled “Layer 3 — brand configuration is code, not server state”

Branding (title, logo, colors, taxonomies, the author roster) lives in git templates per brand over a shared theme. A hand-edit on the web host survives only until the next build — by design: the template is the single source of truth, and every deploy reasserts it. New sites start as staged port-instances (reachable, no domain) and convert to production domains in a short, reversible procedure (DNS → vhost → registry URL → platform record).

Layer 4 — deploying the layers themselves

Section titled “Layer 4 — deploying the layers themselves”

Each layer ships on its own cadence, with its own invariants:

  • Platform — a four-phase deploy script: preflight (refuses while runs are active; snapshots the secret surface), code swap with a timestamped backup, container rebuild, postflight (health-wait + re-hash of the secret surface — an unexpected diff aborts). Database migrations apply automatically at container boot.
  • Hub — rebuilt from its repo; a template change redeploys per instance without touching the platform.
  • This documentation — built statically, shipped as an immutable release directory with a symlink flip; rollback is flipping the symlink back.

Three layers mean three registries of truth (platform DB, hub instance registry, git templates) — and the operational cost is keeping their pointers aligned; misaligned registry state has produced “instance not found” incidents. The verified-live-URL rule also means publishing is only as done as the slowest site build — a queue at the hub delays proof, not just delivery. Both costs are accepted in exchange for fleet-wide brand changes that never require touching the content engine.

Any published article row carries its live URL; fetch it — that URL responding is the platform’s own definition of “published”. The instances API domain lists the fleet the hub serves.

Specs: SPEC-073 (published-URL identity), SPEC-096 (publish-path hardening), SPEC-097 (publish stack), plus the vendored hub service.