Live-checked docker_stacks.yml directly. Total/dockerfile-strategy counts (41/36) and the pinned_manual holdouts were already accurate. But docker-socket-proxy and kodi's mariadb service were wrongly listed as still hitting Docker Hub directly - both actually float a :latest pull against a dedicated in-fleet mirror stack (docker-socket-proxy- mirror, mariadb-latest) built via the normal dockerfile pattern. Fixed the table and documented the real mechanism. Same gap existed in the KB note, fixed there too.
52 lines
6.1 KiB
Markdown
52 lines
6.1 KiB
Markdown
# 📦 Self-Hosted Registry & Build Pipeline
|
|
|
|
> [!NOTE]
|
|
> Gitea's own built-in container registry hosts every image this fleet builds — no separate registry service to operate, no dependency on Docker Hub or ghcr at runtime for the stacks that have opted in. This is the mechanism that makes [`docs/iac-pipeline.md`](iac-pipeline.md)'s "own everything in the stack" philosophy actually true at the container-image layer, not just the OS layer.
|
|
|
|
| | |
|
|
|---|---|
|
|
| **Registry** | Gitea's own built-in container registry |
|
|
| **Trigger** | Weekly schedule via Kestra, not push-triggered |
|
|
| **Rollout** | All 5 Docker hosts converted; the largest has 36 of 41 stacks on this pattern |
|
|
|
|
## Why self-host the registry instead of just using Docker Hub
|
|
|
|
Pulling directly from Docker Hub/ghcr at runtime was the status quo before this existed, and it was replaced for a concrete reason: every container pull became a runtime dependency on an external service staying up and staying reachable through the firewall model in [`docs/firewall-and-network.md`](firewall-and-network.md). Building images in-fleet and pushing to a registry that's already inside the trust boundary means a redeploy never depends on an external registry's availability, and the exact image contents are fully within this fleet's own control rather than whatever an upstream maintainer pushed as `:latest` today.
|
|
|
|
Pushing to the registry uses the same Vault-backed secrets pattern as everything else in [`docs/secrets-and-pki.md`](secrets-and-pki.md), not a token pasted into a CI config somewhere: a Gitea Personal Access Token scoped to `write:package` only, rendered by Vault Agent to a `0600` file on each building host and piped straight into `docker login --password-stdin` — never typed, never sitting in a config file with broader scope than it needs.
|
|
|
|
This is the real registry, not a claim about it — every one of these is a genuine self-built container, not a Docker Hub pull:
|
|
|
|

|
|
|
|
## The cheap-check-first build decision
|
|
|
|

|
|
|
|
> [!TIP]
|
|
> Most stacks use a minimal `FROM <upstream-image>:${VERSION}` wrapper Dockerfile rather than a full clone-and-build-from-source setup — simpler, builds near-instantly, can't break from upstream build tooling changing underneath it, while still achieving the real goal: an in-fleet registry with no direct Docker Hub/ghcr dependency at runtime. A handful of multi-build-image stacks (the ones with more than one real upstream component, like Paperless-ngx, Ghost, Ollama, and the Grafana stack) skip the cheap check entirely, since a single "upstream digest" comparison doesn't make sense when there's more than one upstream involved.
|
|
|
|
## Deliberate holdouts
|
|
|
|
Not every stack is on this pattern, and that's a decision, not an oversight — knowing when *not* to apply a pattern uniformly is as much a design skill as building the pattern in the first place:
|
|
|
|
| Stack | Why it's held out |
|
|
|---|---|
|
|
| Authentik, Immich, tdarr | Complex multi-service upstream builds — not worth replicating a build process the upstream project already maintains well; the maintenance cost of tracking their own build changes would exceed the benefit |
|
|
| Kodi's own app image | Genuinely still a direct Docker Hub dependency — low enough stakes that the extra build step isn't worth it |
|
|
| Nextcloud's own `nextcloud`/`euro-office` services | Pre-existing custom Dockerfiles, left as-is beyond adding `image:` references — its own db/redis still float on Docker Hub tags directly, a conscious "don't touch what already works" call |
|
|
|
|
Two more things that look like holdouts but aren't: `docker-socket-proxy` and Kodi's own `mariadb` service both run on `strategy: latest`, but neither actually depends on Docker Hub at runtime — each floats a `:latest` pull against a small dedicated mirror stack (`docker-socket-proxy-mirror`, `mariadb-latest`) that *is* built in-fleet via the normal pattern. It's a deliberate variant for lightweight consumer containers: one real build feeds every stack that needs that base image, instead of each one maintaining its own near-identical Dockerfile.
|
|
|
|
## Trigger discipline
|
|
|
|
> [!WARNING]
|
|
> This is schedule-triggered, not push-triggered — a deliberate choice, not a missing feature. Editing a stack's own Dockerfile doesn't by itself trigger a rebuild; it waits for the next weekly sweep to find the upstream base image has actually changed, or for someone to force a manual run via [`docs/orchestration.md`](orchestration.md). The tradeoff: a simpler mental model and no rebuild storm on every unrelated commit, at the real cost of same-day propagation for an urgent base-image CVE fix — accepted because a weekly cadence matches this fleet's actual risk tolerance, not because push-triggering would have been harder to build.
|
|
|
|
## Housekeeping
|
|
|
|
Unused local images get pruned automatically at the end of every build pass — for build-and-push-only stacks, the registry is the source of truth, not whatever happens to be sitting on local disk, so nothing is lost by aggressively reclaiming local space immediately. A volume prune runs right after it, for a less obvious reason: build-and-push-only database stacks declare no `volumes:` block of their own, but the base image's own `VOLUME` directive still leaves an anonymous volume behind on every single build regardless — always safe to reclaim immediately, since the prune only ever removes volumes with zero remaining container references.
|
|
|
|
---
|
|
Related: [`docs/iac-pipeline.md`](iac-pipeline.md), [`docs/orchestration.md`](orchestration.md), [`docs/sso-and-identity.md`](sso-and-identity.md) · Stacks: [`stacks/platform/gitea/`](../stacks/platform/gitea/), [`stacks/platform/kestra/`](../stacks/platform/kestra/)
|