3.7 KiB
ADR 0002: Immutable cross-repository releases
- Status: Proposed
- Date: 2026-08-20
Context
The deployment repository composes independently owned backend and frontend repositories. A production release must identify one compatible revision of each repository, survive unavailable source forges, and roll back without rebuilding. The Qlockify reference updates nested server checkouts to mutable branches and builds in place. That is useful for a small deployment but makes the deployed inputs dependent on branch movement, server toolchains, and build timing.
Service repositories also need focused CI without gaining independent authority to mutate the shared production server. Backend, frontend, and deployment changes can arrive close together, so direct per-repository SSH deploys would race and could assemble an unreviewed combination.
Decision
Build each application image once in its owning repository after protected-revision quality gates, publish it to a private OCI registry, and address it in production by immutable digest.
An authenticated backend or frontend event creates a deployment candidate containing its source SHA, image digest, interface/migration compatibility metadata, and provenance references. The event never directly deploys production. The deployment repository is the only release coordinator and serializes production changes under one deployment lock.
Every approved release has a manifest containing the deployment, backend, and frontend SHAs; all image digests; expected migration version; non-secret configuration checksum; provenance/SBOM references; previous release; approval; and health outcome. Production pulls and runs only the digests in that manifest. Server-side source builds, git reset --hard synchronization, branch-name deployment, and floating image tags are not permitted.
Migrations run as a one-off candidate-backend command before API/worker replacement. They use an expand/contract compatibility policy. Application rollback selects the previous manifest and never automatically runs a database down migration.
Alternatives considered
Qlockify-style server checkout and in-place build
- Pros: simple; already proven on the reference server; no registry required.
- Cons: mutable inputs, larger server trust/toolchain surface, slow rollback, weak build provenance, and race risk across three repositories.
- Rejected for production: it cannot prove that a rebuilt server artifact is identical to the CI-tested artifact.
Let each service repository deploy its own services
- Pros: fast and locally owned.
- Cons: backend/frontend/deployment workflows can race; no atomic compatibility manifest; migrations and Nginx/config changes cannot be coordinated safely.
- Rejected: the deployment repository is the approved operational control plane.
Git submodules
- Pros: a deployment commit can pin service SHAs.
- Cons: conflicts with ADR 0001 and the approved ignored-checkout topology; still does not guarantee build-once artifacts.
- Rejected: release manifests provide provenance without merging repository mechanics.
Consequences
- A private registry and trusted CI runner are release dependencies.
- Service CI owns testing/building its artifact; deployment CI owns compatibility, production approval, migration orchestration, health gates, and rollback.
- The server can roll back application images even if the source forge is temporarily unavailable, provided required digests remain in the registry/cache.
- Production rollout events must coalesce/serialize instead of racing.
- Registry retention must protect every digest referenced by the active and rollback manifests.
- Exact Gitea cross-repository event support and runner version remain implementation decisions in the Platform Operations spec.