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.
The big picture
Section titled “The big picture”Host-based routing
Section titled “Host-based routing”The service routes purely by the request Host:
- Apex host (the canonical
GOTIFACTS_BASE_DOMAINor anyGOTIFACTS_ALIAS_DOMAINSentry): 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.
Why a reverse proxy is mandatory
Section titled “Why a reverse proxy is mandatory”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-Gomodernc.org/sqlitedriver, so the binary stays CGO-free) holding site metadata and API keys.sites/<group…>/<slug>/@site/— the published files for each site. The reserved@siteleaf lets a site be both a leaf and a parent of nested sites (so a flat sitedecksand a groupdeckscan 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.
Why this shape
Section titled “Why this shape”- 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.