How TrieOH ships — TheTree builds images, Forgejo Actions + Dagger publishes them, the server runs them, Cloudflare Pages hosts the frontends and docs.
TrieOH is self-hosted on a single server (trieoh@main) with plain Docker Compose — no Swarm, no Kubernetes. The pipeline is split across three places:
Piece
Repo
What it does
Build & publish
TrieOH/TheTree
CI on Forgejo Actions; tags trigger image builds via Dagger and push to the Forgejo container registry
Run
TrieOH/deploy
production compose.yml that runs the published images on the server
Push a tag matching */v* — e.g. identityx/v0.35.3.
Forgejo Actions (the publish.yml workflow, concurrency-grouped so tag pushes queue instead of running in parallel against the shared DinD) runs the Dagger module (.dagger/) to build and push the image to the Forgejo container registry (git.trieoh.com/trieoh/<svc>:<tag>), authenticated with FJ_USER / FJ_TOKEN secrets.
The server pulls the new tag and adopts it:
cd ~/deploy/thetreedocker compose up -d --no-deps
Idempotent — container names, volumes and networks are pinned; the stack name thetree must never change.
Images are pinned by tag in deploy/thetree/compose.yml. Bumping a version = edit the tag, commit, redeploy — git history is the version ledger. Rollback = git checkout <prev> -- compose.yml && docker compose up -d.
Networks: internal (postgres only — not exposed to the host), caddy-net (external, shared with infra — the public gateway), obs-net (external — telemetry scrapes).
Env handling: real .env files live only on the server. Templates are committed with blank values. Never commit a snapshot — docker compose config inlines every resolved secret (learned the hard way: a committed snapshot leaked all prod secrets and had to be scrubbed from history).