#203: pin all third-party deploy images by digest #262
@ -91,6 +91,22 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
exit "$bad"
|
exit "$bad"
|
||||||
|
|
||||||
|
# Third-party deploy images are pinned by digest (issue #203): every
|
||||||
|
# image in the deploy compose that is not one of our own
|
||||||
|
# (${IMAGE_PREFIX}…) must carry @sha256 — the tag stays for
|
||||||
|
# readability, the digest decides what runs. Update procedure:
|
||||||
|
# deploy/stages.md §Third-party image digests. compose.dev.yml is a
|
||||||
|
# local convenience, deliberately not held to this.
|
||||||
|
- name: Third-party compose images are digest-pinned
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
bad=$(grep -hE '^ *image: ' deploy/compose/docker-compose.yml | grep -v 'IMAGE_PREFIX' | grep -v '@sha256:' || true)
|
||||||
|
if [ -n "$bad" ]; then
|
||||||
|
echo "third-party image reference(s) without a digest:"
|
||||||
|
echo "$bad"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Set up pnpm
|
- name: Set up pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: node:22.15-alpine
|
image: node:22.15.1-alpine
|
||||||
build: !reset null
|
build: !reset null
|
||||||
working_dir: /repo
|
working_dir: /repo
|
||||||
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/web dev -- --host 0.0.0.0"
|
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/web dev -- --host 0.0.0.0"
|
||||||
@ -33,7 +33,7 @@ services:
|
|||||||
depends_on: !reset []
|
depends_on: !reset []
|
||||||
|
|
||||||
api:
|
api:
|
||||||
image: node:22.15-alpine
|
image: node:22.15.1-alpine
|
||||||
build: !reset null
|
build: !reset null
|
||||||
working_dir: /repo
|
working_dir: /repo
|
||||||
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/shared build && pnpm --filter @dorfteich/api start:dev"
|
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/shared build && pnpm --filter @dorfteich/api start:dev"
|
||||||
@ -53,7 +53,7 @@ services:
|
|||||||
- pnpm-store:/root/.local/share/pnpm/store
|
- pnpm-store:/root/.local/share/pnpm/store
|
||||||
|
|
||||||
collab:
|
collab:
|
||||||
image: node:22.15-alpine
|
image: node:22.15.1-alpine
|
||||||
build: !reset null
|
build: !reset null
|
||||||
working_dir: /repo
|
working_dir: /repo
|
||||||
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/shared build && pnpm --filter @dorfteich/collab start:dev"
|
command: sh -c "npm i -g pnpm@11 && pnpm install && pnpm --filter @dorfteich/shared build && pnpm --filter @dorfteich/collab start:dev"
|
||||||
|
|||||||
@ -193,7 +193,7 @@ services:
|
|||||||
<<: *logging
|
<<: *logging
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:17.5-alpine
|
image: postgres:17.5-alpine@sha256:6567bca8d7bc8c82c5922425a0baee57be8402df92bae5eacad5f01ae9544daa
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: dorfteich
|
POSTGRES_USER: dorfteich
|
||||||
@ -213,7 +213,7 @@ services:
|
|||||||
# on the internal network only — never exposed. Pinned image; the api reaches
|
# on the internal network only — never exposed. Pinned image; the api reaches
|
||||||
# it at http://pandoc:3030. `wget` ships in the (busybox-based) image.
|
# it at http://pandoc:3030. `wget` ships in the (busybox-based) image.
|
||||||
pandoc:
|
pandoc:
|
||||||
image: pandoc/core:3.6
|
image: pandoc/core:3.6@sha256:5b8a29d9b70d5d8ca766e5d1dcfc41916b23ab79276a80527be70516110f4c1e
|
||||||
command: ['server']
|
command: ['server']
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks: [internal]
|
networks: [internal]
|
||||||
@ -228,7 +228,7 @@ services:
|
|||||||
# on the internal network only — never exposed. Pinned image; the api reaches
|
# 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.
|
# it at http://gotenberg:3000 and posts export HTML to its Chromium route.
|
||||||
gotenberg:
|
gotenberg:
|
||||||
image: gotenberg/gotenberg:8
|
image: gotenberg/gotenberg:8@sha256:67097317623a503ba2a6a7e9ae8db6929a1f7e1bbd88077bacf2d325fbdab923
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks: [internal]
|
networks: [internal]
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@ -244,7 +244,7 @@ services:
|
|||||||
# `localhost` default uses Caddy's internal CA — handy for smoke tests).
|
# `localhost` default uses Caddy's internal CA — handy for smoke tests).
|
||||||
# Instances behind an existing host proxy simply never enable the profile.
|
# Instances behind an existing host proxy simply never enable the profile.
|
||||||
caddy:
|
caddy:
|
||||||
image: caddy:2.10-alpine
|
image: caddy:2.10-alpine@sha256:4c6e91c6ed0e2fa03efd5b44747b625fec79bc9cd06ac5235a779726618e530d
|
||||||
profiles: [caddy]
|
profiles: [caddy]
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@ -122,6 +122,35 @@ The pipeline pushes images to the Gitea container registry
|
|||||||
- On the host, `docker login gitea.101010.cloud` for the `deploy` user
|
- On the host, `docker login gitea.101010.cloud` for the `deploy` user
|
||||||
with a `read:package` token, so `compose pull` works.
|
with a `read:package` token, so `compose pull` works.
|
||||||
|
|
||||||
|
## 5a. Third-party image digests (issue #203, ADR 0024)
|
||||||
|
|
||||||
|
Every third-party image in `deploy/compose/docker-compose.yml` is pinned
|
||||||
|
as `name:tag@sha256:…` — the tag stays for readability, the digest
|
||||||
|
decides what runs, so the deployed artefact is exactly the reviewed one.
|
||||||
|
An early CI step fails on any third-party `image:` reference without a
|
||||||
|
digest. Our own images are pinned per release by the deploy pipeline
|
||||||
|
(`TAG` in the stage `.env`); `compose.dev.yml` is a local convenience and
|
||||||
|
deliberately not digest-pinned.
|
||||||
|
|
||||||
|
**Updating a digest** (e.g. to take a rebased base image or a new tag):
|
||||||
|
|
||||||
|
1. Resolve the new digest — this prints the manifest-list digest every
|
||||||
|
platform pulls:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker buildx imagetools inspect <name:tag> # → Digest: sha256:…
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update the reference in `deploy/compose/docker-compose.yml` to
|
||||||
|
`<name:tag>@sha256:…` and let CI confirm.
|
||||||
|
3. **Roll out by hand**: CD does NOT sync stage composes — apply the same
|
||||||
|
change to `/srv/DOCKER/dorfteich-{test,int,prod}/docker-compose.yml`
|
||||||
|
on ONE. The next `compose pull && up -d` (any CD run for test/int, the
|
||||||
|
next release deploy for prod) recreates the containers from the pinned
|
||||||
|
digest.
|
||||||
|
4. Verify after rollout: `docker inspect --format '{{.Image}}' <container>`
|
||||||
|
must print the pinned digest (or check `RepoDigests` on the image).
|
||||||
|
|
||||||
## 6. Verification checklist
|
## 6. Verification checklist
|
||||||
|
|
||||||
- [ ] `https://test.dorfteich.cloud/healthz` → `ok`
|
- [ ] `https://test.dorfteich.cloud/healthz` → `ok`
|
||||||
|
|||||||
@ -69,7 +69,7 @@ Digest-Pinning (#203) läuft vorgezogen in `M25`_
|
|||||||
Hochgezogen, weil das eine Frage im **ersten** Behördengespräch ist. „Sollte
|
Hochgezogen, weil das eine Frage im **ersten** Behördengespräch ist. „Sollte
|
||||||
gehen" ist dort eine schlechtere Antwort als „getestet, hier ist die Anleitung".
|
gehen" ist dort eine schlechtere Antwort als „getestet, hier ist die Anleitung".
|
||||||
|
|
||||||
- [ ] Alle Images auf Digest pinnen (schließt den `gotenberg:8`-Punkt ein) · 1 AT · #203
|
- [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
|
- [ ] Mirror-Verfahren in interne Registry dokumentieren · 1 AT · #218
|
||||||
- [ ] Build ohne Netz reproduzierbar (pnpm Offline-Store / reine
|
- [ ] Build ohne Netz reproduzierbar (pnpm Offline-Store / reine
|
||||||
Prebuilt-Images) · 2–3 AT · #219
|
Prebuilt-Images) · 2–3 AT · #219
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user