dorfteich/docs/architecture/adr/0024-reproducible-offline-deployment.md
Claude Fable 5 a758c9d78b
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 6m45s
CI / Build container images (pull_request) Successful in 1m14s
CI / Auth e2e pack (pull_request) Successful in 8m37s
CI / Import/export fidelity gate (pull_request) Successful in 57s
CD / Build and push images (push) Successful in 21s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 28s
CI / Lint, typecheck, test (push) Successful in 6m19s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m12s
CI / Import/export fidelity gate (push) Successful in 58s
#219: verified reproducible build without network access
The ADR 0024 §4 decision, taken explicitly and both ways: customers
OPERATE prebuilt digest-pinned images (no customer-side build), and
ADDITIONALLY the workspace build is verified to work with networking
disabled - so site-local patching stays possible without internet.

Evidence (docs/vs-nfd/96-offline-build-protokoll.md): pnpm install
--offline --frozen-lockfile plus pnpm build under docker run
--network none (node:22.15.1-alpine + pnpm 11.9.0, the pinned
toolchain), reproduced twice from clean checkouts with identical
results. The offline kit is the pnpm store (~870 MB) plus the build
user's ~/.cache (~460 MB - the prisma engines live there; without the
cache the prisma postinstall fails offline).

The one network dependency found and bounded: the drawio plugin's
installable ZIP fetches its pinned vendor tarball on first build.
Deploy images contain no plugin ZIPs, so the delivery-relevant build is
fully offline (CI=1 skips the fetch, as in CI); an offline ZIP build
pre-seeds the tarball into packages/plugins/drawio/vendor/.

Also catches up the operations manual's scheduler-job table to 10
(read-trail-maintenance was added in #224 without the row here).

Refs #219.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
2026-07-31 14:39:35 +02:00

85 lines
4.3 KiB
Markdown

# ADR 0024: Reproducible offline deployment
- Status: proposed
- Date: 2026-07-29
## Context
A VS zone has no internet egress. "Should work offline" is the answer that
loses a first meeting; "tested, here is the procedure" is the one that
wins it — which is why the plan pulled this out of the roadmap into
Phase 1.
The current state is favourable but unverified. There is no telemetry, no
update check, no CDN; fonts are self-hosted (ADR 0016); CSP is
`default-src 'self'`; search is Postgres rather than an external engine;
the drawio plugin is vendored rather than loaded from a remote editor. What
is missing is evidence, plus two real gaps: images are referenced by tag
(including the floating `gotenberg/gotenberg:8`), and there is no
documented mirror or update path.
## Decision
1. **All third-party images are pinned by digest** (`name:tag@sha256:…`).
The tag stays for human readability; the digest decides what runs. A CI
check rejects any un-digested third-party reference.
2. **The image list is generated, not hand-maintained**, so a mirror
procedure cannot silently miss a service.
3. **An internal registry is the supported source.** Compose takes the
registry prefix from configuration; no site edits image references.
4. **Reproducibility without network is a stated choice between two
paths**: an offline pnpm store enabling `install` + `build` with
networking disabled, **or** prebuilt images only with no customer-side
build. Either is acceptable; leaving it unstated is not, because it
determines whether the customer can patch locally.
5. **The airgap claim is proven by a documented run** in a network-isolated
environment, covering every function including the export sidecars, and
listing every outbound connection attempt observed. This run is the
artefact, and it also answers the plan's open question about what breaks
offline.
6. **The offline update path is part of the decision, not an afterthought**:
bundle, verify by digest, back up, apply, verify, roll back — with the
irreversibility of migrations stated explicitly.
## Decisions taken in #219
- **Both paths, with stated roles**: customers OPERATE prebuilt,
digest-pinned images (the CI release build; mirrored per #218) — no
customer-side build for operation. ADDITIONALLY the workspace build is
verified to work with networking disabled, so site-local patching
remains possible (open source as an audit advantage) — the "no local
patching" consequence therefore does not apply.
- **The offline kit** is the pnpm store plus the build user's `~/.cache`
(the prisma postinstall pulls its engines from there instead of the
network) — both filled by one online `pnpm install --frozen-lockfile`.
- **Evidence**: `pnpm install --offline --frozen-lockfile` and
`pnpm build` under `docker run --network none` (node:22.15.1-alpine,
pnpm 11.9.0 — the pinned toolchain), reproduced twice from clean
checkouts (`docs/vs-nfd/96-offline-build-protokoll.md`).
- **The one network dependency found**: the drawio plugin's installable
ZIP fetches its pinned vendor tarball on first build. Deploy images
contain no plugin ZIPs, so the delivery-relevant build is fully
offline (`CI=1` skips the fetch, as in CI); for an offline ZIP build
the tarball is pre-seeded into `packages/plugins/drawio/vendor/`.
## Consequences
- Digest pinning creates recurring maintenance: security updates now
require an explicit, reviewable change. That visibility is the point.
- Digest pinning must precede the mirror and update work, so it sits in the
`hardening & supply chain` milestone rather than this one.
- The isolated test run will surface findings; each becomes its own issue
referenced from #220 rather than expanding that issue's scope.
- Outbound SMTP is the one connection an authority may or may not permit;
the deployment must be functional without it, and the consequences of
disabling it (no notifications, no verification mail — which interacts
with `auth.local.enabled = false`) are documented.
- CD does not sync stage composes, so digest changes need an explicit
rollout step on the stage hosts.
## Implementing issues
#203 (digest pinning), #218 (registry mirror), #219 (network-free build),
#220 (isolated test run), #221 (offline update path), #236 (pinned Node
version — added from the Ist-Aufnahme, I-26).