#218: mirror procedure into an internal registry #286
@ -19,6 +19,12 @@ COLLAB_TOKEN_SECRET=change-me-to-a-long-random-string
|
||||
IMAGE_PREFIX=dorfteich
|
||||
# Image tag to run: a git SHA, `test`, `int`, or a release tag like v1.2.0.
|
||||
TAG=latest
|
||||
# Registry prefix for THIRD-PARTY images (postgres, pandoc, gotenberg,
|
||||
# caddy) — for airgapped sites pulling from an internal mirror
|
||||
# (issue #218, ADR 0024). Must end with a slash, e.g.
|
||||
# `registry.example.gov/mirror/`. Empty = public registries. Own images
|
||||
# are covered by IMAGE_PREFIX above, which may equally carry a registry.
|
||||
REGISTRY_PREFIX=
|
||||
|
||||
# --- ports (localhost only; the host reverse proxy routes to these) ---------
|
||||
# Suggested per stage on the shared host (ONE): test 8100/8101/8102,
|
||||
|
||||
@ -193,7 +193,7 @@ services:
|
||||
<<: *logging
|
||||
|
||||
db:
|
||||
image: postgres:17.5-alpine@sha256:6567bca8d7bc8c82c5922425a0baee57be8402df92bae5eacad5f01ae9544daa
|
||||
image: ${REGISTRY_PREFIX:-}postgres:17.5-alpine@sha256:6567bca8d7bc8c82c5922425a0baee57be8402df92bae5eacad5f01ae9544daa
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: dorfteich
|
||||
@ -213,7 +213,7 @@ services:
|
||||
# on the internal network only — never exposed. Pinned image; the api reaches
|
||||
# it at http://pandoc:3030. `wget` ships in the (busybox-based) image.
|
||||
pandoc:
|
||||
image: pandoc/core:3.6@sha256:5b8a29d9b70d5d8ca766e5d1dcfc41916b23ab79276a80527be70516110f4c1e
|
||||
image: ${REGISTRY_PREFIX:-}pandoc/core:3.6@sha256:5b8a29d9b70d5d8ca766e5d1dcfc41916b23ab79276a80527be70516110f4c1e
|
||||
command: ['server']
|
||||
restart: unless-stopped
|
||||
networks: [internal]
|
||||
@ -228,7 +228,7 @@ services:
|
||||
# on the internal network only — never exposed. Pinned image; the api reaches
|
||||
# it at http://gotenberg:3000 and posts export HTML to its Chromium route.
|
||||
gotenberg:
|
||||
image: gotenberg/gotenberg:8@sha256:67097317623a503ba2a6a7e9ae8db6929a1f7e1bbd88077bacf2d325fbdab923
|
||||
image: ${REGISTRY_PREFIX:-}gotenberg/gotenberg:8@sha256:67097317623a503ba2a6a7e9ae8db6929a1f7e1bbd88077bacf2d325fbdab923
|
||||
restart: unless-stopped
|
||||
networks: [internal]
|
||||
healthcheck:
|
||||
@ -244,7 +244,7 @@ services:
|
||||
# `localhost` default uses Caddy's internal CA — handy for smoke tests).
|
||||
# Instances behind an existing host proxy simply never enable the profile.
|
||||
caddy:
|
||||
image: caddy:2.10-alpine@sha256:4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d
|
||||
image: ${REGISTRY_PREFIX:-}caddy:2.10-alpine@sha256:4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d
|
||||
profiles: [caddy]
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
|
||||
17
deploy/scripts/list-images.sh
Executable file
17
deploy/scripts/list-images.sh
Executable file
@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# The complete image list of the deploy stack, GENERATED from the compose
|
||||
# file (issue #218, ADR 0024) — a mirror procedure that works from a
|
||||
# hand-maintained list will silently miss a service one day.
|
||||
#
|
||||
# Usage:
|
||||
# TAG=v1.2.0 IMAGE_PREFIX=gitea.101010.cloud/stwaidele/dorfteich \
|
||||
# deploy/scripts/list-images.sh
|
||||
#
|
||||
# REGISTRY_PREFIX is deliberately left empty here: the list names the
|
||||
# UPSTREAM references (with digests) that a mirror copies FROM.
|
||||
set -eu
|
||||
cd "$(dirname "$0")/../compose"
|
||||
COMPOSE_PROJECT_NAME=dorfteich-list \
|
||||
POSTGRES_PASSWORD=unused COLLAB_TOKEN_SECRET=unused \
|
||||
REGISTRY_PREFIX= \
|
||||
docker compose -f docker-compose.yml --profile caddy config --images | sort -u
|
||||
@ -151,6 +151,64 @@ deliberately not digest-pinned.
|
||||
4. Verify after rollout: `docker inspect --format '{{.Image}}' <container>`
|
||||
must print the pinned digest (or check `RepoDigests` on the image).
|
||||
|
||||
## 5b. Mirroring into an internal registry (issue #218, ADR 0024)
|
||||
|
||||
Airgapped sites pull every image from their own registry. The procedure
|
||||
is written for the customer's operations team — nothing in it needs this
|
||||
project's infrastructure.
|
||||
|
||||
1. **Generate the image list** (never hand-maintain it — a manual list
|
||||
silently misses a service one day). On a machine with this repository
|
||||
and Docker:
|
||||
|
||||
```bash
|
||||
TAG=v0.12.0 IMAGE_PREFIX=gitea.101010.cloud/stwaidele/dorfteich \
|
||||
deploy/scripts/list-images.sh
|
||||
```
|
||||
|
||||
This resolves `deploy/compose/docker-compose.yml` (including the
|
||||
optional caddy profile) and prints every reference: our four images
|
||||
at the release tag plus the digest-pinned third-party images.
|
||||
|
||||
2. **Copy each image digest-preservingly.** The copy tool is
|
||||
`docker buildx imagetools create` — it moves manifests
|
||||
registry-to-registry without re-encoding, so the digest in the mirror
|
||||
is byte-identical to the pinned one:
|
||||
|
||||
```bash
|
||||
docker buildx imagetools create \
|
||||
--tag registry.example.gov/mirror/postgres:17.5-alpine \
|
||||
postgres:17.5-alpine@sha256:6567bca8d7bc8c82c5922425a0baee57be8402df92bae5eacad5f01ae9544daa
|
||||
```
|
||||
|
||||
(One command per listed image; our own images copy the same way from
|
||||
the release registry. Fallback when `imagetools create` stalls against
|
||||
a registry — observed once with the Gitea registry: plain
|
||||
`docker pull` + `docker tag` + `docker push` is equally valid, because
|
||||
step 3's digest comparison closes the loop either way.)
|
||||
|
||||
3. **Verify the digest after the copy** — the copy is only evidence once
|
||||
checked:
|
||||
|
||||
```bash
|
||||
docker buildx imagetools inspect registry.example.gov/mirror/postgres:17.5-alpine
|
||||
# → Digest: must equal the pinned sha256 from the compose file
|
||||
```
|
||||
|
||||
4. **Point the deployment at the mirror** — configuration only, no image
|
||||
reference is ever edited per site:
|
||||
- third-party images: `REGISTRY_PREFIX=registry.example.gov/mirror/`
|
||||
in the stage `.env` (must end with `/`),
|
||||
- own images: `IMAGE_PREFIX=registry.example.gov/mirror/dorfteich`.
|
||||
|
||||
The digest pins still apply — Docker verifies the pulled content
|
||||
against the same `sha256` regardless of which registry serves it.
|
||||
|
||||
Executed end-to-end on 2026-07-31 against a local `registry:2` (all four
|
||||
third-party images plus `dorfteich-api:v0.12.0`; every mirrored digest
|
||||
identical to the pin) — protocol:
|
||||
`docs/vs-nfd/95-mirror-protokoll.md`.
|
||||
|
||||
## 6. Verification checklist
|
||||
|
||||
- [ ] `https://test.dorfteich.cloud/healthz` → `ok`
|
||||
|
||||
@ -70,7 +70,7 @@ Hochgezogen, weil das eine Frage im **ersten** Behördengespräch ist. „Sollte
|
||||
gehen" ist dort eine schlechtere Antwort als „getestet, hier ist die Anleitung".
|
||||
|
||||
- [x] Alle Images auf Digest pinnen (schließt den `gotenberg:8`-Punkt ein) · 1 AT · #203
|
||||
- [ ] Mirror-Verfahren in interne Registry dokumentieren · 1 AT · #218
|
||||
- [x] Mirror-Verfahren in interne Registry dokumentieren · 1 AT · #218
|
||||
- [ ] Build ohne Netz reproduzierbar (pnpm Offline-Store / reine
|
||||
Prebuilt-Images) · 2–3 AT · #219
|
||||
- [ ] Testlauf in netzisolierter Umgebung, Protokoll als Beleg · 2 AT · #220
|
||||
|
||||
@ -46,11 +46,21 @@ Belegstufe: ✅ erprobt — die Stages Test/Int/Prod auf ONE sind exakt
|
||||
nach dieser Prozedur aufgesetzt und laufen produktiv (`deploy/stages.md`
|
||||
dokumentiert die realen Instanzen).
|
||||
|
||||
**Airgap-/Offline-Variante:** ⏳ offen — Mirror-Verfahren (#218),
|
||||
netzloser Build (#219), Testlauf in isolierter Umgebung (#220),
|
||||
Offline-Update-Pfad (#221); Meilenstein M28. Bereits vorhanden als
|
||||
Grundlage: alle Dritt-Images digest-gepinnt (#203), ein authoritativer
|
||||
Node-Pin (#236), SBOMs je Release (#202).
|
||||
**Airgap-/Offline-Variante:** teilweise verfügbar —
|
||||
|
||||
- **Mirror-Verfahren (#218): ✅ verfügbar und einmal end-to-end
|
||||
ausgeführt.** Schrittfolge: Image-Liste generieren
|
||||
(`deploy/scripts/list-images.sh` — nie von Hand pflegen),
|
||||
digest-erhaltend kopieren (`docker buildx imagetools create`), Digest
|
||||
im Spiegel gegen den Compose-Pin verifizieren, Deployment per
|
||||
`REGISTRY_PREFIX` (Dritt-Images) bzw. `IMAGE_PREFIX` (eigene Images)
|
||||
auf den Spiegel zeigen. Vollständige Prozedur:
|
||||
`deploy/stages.md` §5b; Ausführungsnachweis:
|
||||
`95-mirror-protokoll.md`.
|
||||
- ⏳ offen: netzloser Build (#219), Testlauf in isolierter Umgebung
|
||||
(#220), Offline-Update-Pfad (#221); Meilenstein M28. Bereits vorhanden
|
||||
als Grundlage: alle Dritt-Images digest-gepinnt (#203), ein
|
||||
authoritativer Node-Pin (#236), SBOMs je Release (#202).
|
||||
|
||||
## 2 Update und Rollback
|
||||
|
||||
|
||||
55
docs/vs-nfd/95-mirror-protokoll.md
Normal file
55
docs/vs-nfd/95-mirror-protokoll.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Mirror-Protokoll — Spiegelung in eine interne Registry (Issue #218)
|
||||
|
||||
Nachweis der einmaligen End-to-End-Ausführung des Mirror-Verfahrens aus
|
||||
`deploy/stages.md` §5b (ADR 0024). Prüfer-taugliche Belegstufe: **live
|
||||
verifiziert** — jede Zeile unten ist die tatsächliche Ausgabe des
|
||||
Verifikationsschritts, nicht eine Erwartung.
|
||||
|
||||
- **Datum:** 2026-07-31
|
||||
- **Umgebung:** Dritt-Images: macOS-Arbeitsplatz (OrbStack-Docker) mit
|
||||
Ziel-Registry `registry:2` auf `localhost:5001`; eigenes Image: Host
|
||||
ONE mit Ziel-Registry `registry:2` auf `127.0.0.1:5002` —
|
||||
stellvertretend für die interne Registry der Behörde; das Verfahren
|
||||
ist registry-agnostisch.
|
||||
- **Quelle der Image-Liste:** `deploy/scripts/list-images.sh` mit
|
||||
`TAG=v0.12.0` (der aktuelle Prod-Stand), generiert aus
|
||||
`deploy/compose/docker-compose.yml` inkl. caddy-Profil.
|
||||
- **Kopierwerkzeug:** Dritt-Images per `docker buildx imagetools
|
||||
create` (kopiert Manifeste registry-zu-registry ohne Re-Encoding —
|
||||
digest-erhaltend). Eigenes Image per `docker pull` + `tag` + `push`;
|
||||
der Digest-Vergleich schließt die Schleife (Beobachtung 31.07.:
|
||||
`imagetools create` stallte vom Arbeitsplatz gegen die Gitea-Registry,
|
||||
während die Registry-API sofort antwortete — der pull/push-Weg ist der
|
||||
robuste Ausweich, solange der Digest verifiziert wird).
|
||||
|
||||
## Ergebnis je Image
|
||||
|
||||
Verifikation jeweils per `docker buildx imagetools inspect
|
||||
localhost:5001/mirror/<name>` — der Digest im Spiegel muss dem Pin aus
|
||||
der Compose-Datei gleichen.
|
||||
|
||||
| Image | Gepinnter Digest (Compose) | Digest im Spiegel | Ergebnis |
|
||||
| ----------------------- | ------------------------------------------------------------------------- | ----------------- | -------- |
|
||||
| `caddy:2.10-alpine` | `sha256:4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d` | identisch | OK |
|
||||
| `postgres:17.5-alpine` | `sha256:6567bca8d7bc8c82c5922425a0baee57be8402df92bae5eacad5f01ae9544daa` | identisch | OK |
|
||||
| `pandoc/core:3.6` | `sha256:5b8a29d9b70d5d8ca766e5d1dcfc41916b23ab79276a80527be70516110f4c1e` | identisch | OK |
|
||||
| `gotenberg/gotenberg:8` | `sha256:67097317623a503ba2a6a7e9ae8db6929a1f7e1bbd88077bacf2d325fbdab923` | identisch | OK |
|
||||
| `dorfteich-api:v0.12.0` | `sha256:576f16467563871a9655059fc9cdcce49e3951b54a8697065081a9a6832d2294` | identisch | OK |
|
||||
|
||||
(Die drei übrigen eigenen Images `web`/`collab`/`backup` folgen exakt
|
||||
demselben Kommando mit anderem Namen; das Verfahren ist je Image
|
||||
identisch und die Liste kommt generiert aus dem Skript — stellvertretend
|
||||
wurde `api` als größtes eigenes Image kopiert.)
|
||||
|
||||
## Abweichungen
|
||||
|
||||
Alle kopierten Digests stimmen mit den Pins bzw. dem Quell-Digest
|
||||
überein. Einzige Beobachtung: der Werkzeug-Stall von `imagetools
|
||||
create` gegen die Gitea-Registry (oben) — dokumentiert samt Ausweichweg
|
||||
in `deploy/stages.md` §5b.
|
||||
|
||||
## Pflege
|
||||
|
||||
Bei jedem Digest-Update (`deploy/stages.md` §5a) und jedem Release
|
||||
wiederholt der Betreiber die Schritte 1–3 aus §5b für die geänderten
|
||||
Images; die Liste kommt IMMER frisch aus `list-images.sh`.
|
||||
Loading…
Reference in New Issue
Block a user