Files
public/docs/registry-and-build.md
T
thesabear 1382a63dae docs: deepen observability, notifications, orchestration, registry, and dashy batch
Same treatment as the security batch - real tech names, 'why this vs
the alternative' reasoning, heavy cross-linking, ranked/specific
tradeoffs instead of generic hedges.
2026-08-22 08:06:47 +03:00

55 lines
4.8 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.
## The cheap-check-first build decision
```mermaid
flowchart TD
Start["Weekly Kestra trigger<br/>see docs/orchestration.md"] --> Check["Cheap digest check<br/>metadata only, no pull"]
Check --> Match{"Upstream digest ==<br/>fleet registry digest?"}
Match -->|yes| Nothing["Nothing runs<br/>no pull, no build, no push"]
Match -->|no| Pull["Pull base images"]
Pull --> Build["docker compose build --no-cache"]
Build --> Compare["Compare pre/post image IDs"]
Compare --> Push["Push what actually changed"]
Push --> Redeploy["Redeploy via shared<br/>health-check/rollback path"]
```
> [!TIP]
> Most stacks use a minimal wrapper Dockerfile around the upstream image 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 the identity provider in [`docs/sso-and-identity.md`](sso-and-identity.md)) 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 type | Why it's held out |
|---|---|
| Complex multi-service upstream builds (e.g. the identity provider, a photo-management app, a media transcoding tool) | Not worth replicating a build process someone else already maintains well — the maintenance cost of tracking upstream's own build changes would exceed the benefit |
| A docker-socket-proxy style stack | Simple enough that a custom build adds no real value over the upstream image directly |
| One database service still on `latest` | Deliberately not pinned to this pattern — low enough stakes that the extra build step isn't worth it |
| A productivity suite with pre-existing custom Dockerfiles | Left as-is beyond adding image references — its own DB/cache still float on Docker Hub tags directly, a conscious "don't touch what already works" call |
## 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.
---
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/)