ADR 0020: Registry-Based CD (Build on CI, Pull on Server)
ADR 0020: Registry-Based CD (Build on CI, Pull on Server)
Section titled “ADR 0020: Registry-Based CD (Build on CI, Pull on Server)”- Status: Accepted
- Date: 2026-04-14
- Deciders: Ideony team
Context
Section titled “Context”The production server is a Hetzner CAX11 (2 vCPU ARM64, 4 GB RAM). Running docker build on the server during deployment caused out-of-memory kills: the TypeScript compiler + SWC + Prisma generate combined exhaust available RAM, crashing the build mid-step. A different build strategy was needed.
Decision
Section titled “Decision”Build the Docker image in GitHub Actions CI (on ubuntu-latest with 7 GB RAM, cross-compiling for linux/arm64 via QEMU). Push the tagged image to GitHub Container Registry (ghcr.io/acidrums7/ideony-api:<sha>). Deploy step SSH-es into the server and runs docker pull <image>:<sha> && docker stop ideony-api && docker run .... The server never runs docker build.
Consequences
Section titled “Consequences”- CI runners (7 GB RAM) handle the full TypeScript + SWC + Prisma build without OOMs.
- Pull-based deploy: server downloads a pre-built image; startup is fast and deterministic.
- GHCR image tagged with git SHA enables precise rollback (
docker run ghcr.io/.../ideony-api:<previous-sha>). - Trivy vulnerability scan runs against the pushed image in CI before deploy is triggered.
- Blue-green swap + health check in deploy script; auto-rollback on failed health check.
- Cross-compilation (
linux/arm64onx86_64) via QEMU is 4–8× slower than native build; CI push takes ~8–12 min. Acceptable for MVP 0. - GHCR requires
GITHUB_TOKEN/ PAT withpackages:writein CI andpackages:readon the server (Docker login). - Old image tags accumulate in GHCR; a periodic retention policy (keep last 10 tags) must be configured.
Alternatives considered
Section titled “Alternatives considered”- Build on server — rejected: CAX11 OOMs during TypeScript + SWC + Prisma generate; build killed mid-step.
- Dokploy native build (Nixpacks) — considered: Dokploy’s Nixpacks builder also runs on CAX11; same OOM problem.
- Fly.io remote builder — rejected: would require switching hosting provider; out of scope.
- Smaller base image / incremental build — considered: multi-stage Dockerfile already minimises final image; the compile stage itself (not the final image) is the OOM source; compile must happen on CI.