Skip to content

Architecture

gotifacts is a single Go service that does two things: it hosts static sites addressed by hostname, and it serves a portal to browse them. It is deliberately small — one static binary, SQLite, and a volume.

ClientReverse proxy(operator-provided: TLS, forward-auth/SSO)Portal UI + management APIMachine publish API (API key)Static site contentVolume (rw)/data/gotifacts.db/data/sites/<group>/<slug>/@site/gotifacts (Go, static scratch binary) — HTTP :8080Host routerportal + /api + /ingestserve site files Host == baseelseapex / and /api/* — forward-auth ONapex /ingest/* — forward-auth OFF*.base, *.*.base

The service routes purely by the request Host:

  • Apex host (the canonical GOTIFACTS_BASE_DOMAIN or any GOTIFACTS_ALIAS_DOMAINS entry): serves the portal SPA, the management API (/api/*), and the ingest API (/ingest/*).
  • Any other host: maps the host to a site directory under /data/sites/… and serves static files.

The host→directory mapping is the URL ⇄ path convention. This is why no domains are hardcoded: the apex domains are configured, and everything else is derived from them. Alias domains share one site tree — a site published once is reachable under every configured domain — but generated URLs always use the canonical GOTIFACTS_BASE_DOMAIN, which is also the single OAuth issuer.

gotifacts serves plain HTTP on one port and never TLS. It deliberately does not implement TLS termination, certificate management, or SSO. Those are solved problems that your proxy already does well, and keeping them out lets gotifacts stay a tiny, auditable binary. The proxy provides TLS and forward-auth; gotifacts enforces its own authorization on top. See the auth model.

The only state is the volume:

  • gotifacts.db — a SQLite registry (via the pure-Go modernc.org/sqlite driver, so the binary stays CGO-free) holding site metadata and API keys.
  • sites/<group…>/<slug>/@site/ — the published files for each site. The reserved @site leaf lets a site be both a leaf and a parent of nested sites (so a flat site decks and a group decks can coexist); see the URL ⇄ path convention.

Publishing writes to a temp dir on the same volume, validates the upload, then atomically swaps it into place — so a site is never served half-written.

  • One static binary (FROM scratch) is trivial to deploy and audit, and has a minimal attack surface.
  • SQLite + a volume means no external database to operate; back up the volume and you’ve backed up everything.
  • Configuration only via environment keeps deployments reproducible and twelve-factor friendly.