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.
This commit is contained in:
thesabear committed 2026-08-22 08:06:47 +03:00
1 parent 12e69acde7
commit 1382a63dae
5 files changed
+88 -71

No files matched your search

+17 -13
View File
@@ -1,19 +1,23 @@
# 📦 Self-Hosted Registry & Build Pipeline
> [!NOTE]
> Docker images are built and hosted in-fleet rather than pulled from Docker Hub/ghcr directly — for every stack opted into this pattern, at runtime.
> 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** | The Git server's own built-in container registry — no separate registry service to run |
| **Trigger** | Weekly schedule, not push-triggered |
| **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 trigger"] --> Check["Cheap digest check<br/>metadata only, no pull"]
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"]
@@ -24,27 +28,27 @@ flowchart TD
```
> [!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, 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 skip the cheap check entirely, since a single "upstream digest" comparison doesn't make sense when there's more than one real upstream involved.
> 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:
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 | Not worth replicating a build process someone else already maintains well |
| A docker-socket-proxy style stack | Simple enough that a custom build adds no value |
| One database service still on `latest` | Deliberately not pinned to this pattern |
| One stack with pre-existing custom Dockerfiles | Left as-is beyond adding image references — its own DB/cache still float on upstream tags directly |
| 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. 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. A deliberate tradeoff: simpler mental model, avoids a rebuild storm on every unrelated commit, at the cost of same-day propagation for an urgent base-image CVE fix.
> 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.
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 stacks: [`stacks/platform/gitea/`](../stacks/platform/gitea/), [`stacks/platform/kestra/`](../stacks/platform/kestra/)
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/)