CI should build the production Docker image to catch Dockerfile/peer-dep regressions #103

Closed
opened 2026-07-15 19:51:33 +00:00 by claude-bot · 1 comment
Contributor

Severity: Low · Confidence: High · Effort: S · Category: ops

Evidence

  • .forgejo/workflows/ci.yml:52-60 - uses npm ci + npm run build; typechecks both packages (good).
  • api/Dockerfile:12,27 - uses npm install --legacy-peer-deps; also does a manual cp -r src/db/migrations dist/db/migrations.

Problem
CI typechecks and tests both packages (the biggest risk), but never exercises the multi-stage Docker build, the differing dependency resolution (npm install --legacy-peer-deps vs npm ci), or the manual migrations-copy step. A Dockerfile/peer-dep regression ships unverified (the v5.0.0 → v5.0.1 @types/multer incident is the precedent).

Impact
Build-only regressions escape CI and surface at deploy time.

Fix
Add a CI job that builds the production api image (no push) so the Dockerfile path is exercised.

Acceptance criteria

  • CI fails if the production api image fails to build.

Filed from the 2026-07-15 codebase audit. Full report: docs/.internal/report-2026-07-15.md (gitignored).

**Severity:** Low · **Confidence:** High · **Effort:** S · Category: ops **Evidence** - `.forgejo/workflows/ci.yml:52-60` - uses `npm ci` + `npm run build`; typechecks both packages (good). - `api/Dockerfile:12,27` - uses `npm install --legacy-peer-deps`; also does a manual `cp -r src/db/migrations dist/db/migrations`. **Problem** CI typechecks and tests both packages (the biggest risk), but never exercises the multi-stage Docker build, the differing dependency resolution (`npm install --legacy-peer-deps` vs `npm ci`), or the manual migrations-copy step. A Dockerfile/peer-dep regression ships unverified (the v5.0.0 → v5.0.1 `@types/multer` incident is the precedent). **Impact** Build-only regressions escape CI and surface at deploy time. **Fix** Add a CI job that builds the production api image (no push) so the Dockerfile path is exercised. **Acceptance criteria** - [ ] CI fails if the production api image fails to build. --- _Filed from the 2026-07-15 codebase audit. Full report: `docs/.internal/report-2026-07-15.md` (gitignored)._
Author
Contributor

Done in 109c53a (v7.2.0). The new job passed on its first real CI run.

New Images — production build job builds both production images (no push) — api and app:

- run: docker build --target production -t tealeaves-api:ci ./api
- run: docker build -t tealeaves-app:ci ./app

This exercises exactly what npm ci + npm run build cannot: the multi-stage build, npm install --legacy-peer-deps resolving differently from npm ci, and the manual migrations copy.

Plus a guard the issue didn't ask for but the evidence implies. The issue calls out cp -r src/db/migrations dist/db/migrations as unexercised. A build can succeed and still ship an image with no migrations — tsc emits only .js, so if that copy ever breaks you get an image that boots happily and silently skips every migration. That's a data-integrity failure, not a build failure, so a build-only check wouldn't catch it:

- name: Verify migrations were copied into the image
  run: |
    count=$(docker run --rm --entrypoint sh tealeaves-api:ci -c 'ls dist/db/migrations/*.sql | wc -l')
    test "$count" -gt 0

Currently reports 45.

Two infrastructure notes:

  1. The workflow's own comment was stale. It said "Image build/push is intentionally omitted: the Actions runner exposes no Docker daemon to jobs". That described the old docker-host runner, removed 2026-07-01 (iac-repo#32). The dedicated runner VM mounts the DinD socket into every job container on purposeforgejo-runner-ansible's forgejo_runner_container_options: "-v /var/run/docker.sock:/var/run/docker.sock", commented "so docker build works inside CI jobs". Comment corrected.
  2. Runs on ubuntu-latest, not docker. The docker label resolves to a node image with no docker CLI; catthehacker/ubuntu:act-24.04 ships docker 29.6.1 + node 24 + git. Confirmed by running the image before writing the workflow rather than finding out via a red build.

Acceptance criteria:

  • CI fails if the production api image fails to build.

Verified twice: replicated the exact build commands on the dev server first, then the real job went green in CI. Notably it also passed during the run where the API job flaked (iac-repo#159), which is itself a useful datapoint — the heaviest job is not the one failing.

Unblocks part of #134 (auto-deploy): with images building in CI and Forgejo's own container registry available on this instance, the registry-based CD path no longer needs anything stood up.

Done in `109c53a` (v7.2.0). **The new job passed on its first real CI run.** New `Images — production build` job builds **both** production images (no push) — api and app: ```yaml - run: docker build --target production -t tealeaves-api:ci ./api - run: docker build -t tealeaves-app:ci ./app ``` This exercises exactly what `npm ci` + `npm run build` cannot: the multi-stage build, `npm install --legacy-peer-deps` resolving differently from `npm ci`, and the manual migrations copy. **Plus a guard the issue didn't ask for but the evidence implies.** The issue calls out `cp -r src/db/migrations dist/db/migrations` as unexercised. A build can *succeed* and still ship an image with no migrations — `tsc` emits only `.js`, so if that copy ever breaks you get an image that boots happily and **silently skips every migration**. That's a data-integrity failure, not a build failure, so a build-only check wouldn't catch it: ```yaml - name: Verify migrations were copied into the image run: | count=$(docker run --rm --entrypoint sh tealeaves-api:ci -c 'ls dist/db/migrations/*.sql | wc -l') test "$count" -gt 0 ``` Currently reports 45. **Two infrastructure notes:** 1. **The workflow's own comment was stale.** It said *"Image build/push is intentionally omitted: the Actions runner exposes no Docker daemon to jobs"*. That described the old docker-host runner, removed 2026-07-01 (iac-repo#32). The dedicated runner VM mounts the DinD socket into every job container **on purpose** — `forgejo-runner-ansible`'s `forgejo_runner_container_options: "-v /var/run/docker.sock:/var/run/docker.sock"`, commented *"so `docker build` works inside CI jobs"*. Comment corrected. 2. **Runs on `ubuntu-latest`, not `docker`.** The `docker` label resolves to a node image with no docker CLI; `catthehacker/ubuntu:act-24.04` ships docker 29.6.1 + node 24 + git. Confirmed by running the image before writing the workflow rather than finding out via a red build. **Acceptance criteria:** - [x] CI fails if the production api image fails to build. Verified twice: replicated the exact build commands on the dev server first, then the real job went green in CI. Notably it also passed during the run where the API job flaked (iac-repo#159), which is itself a useful datapoint — the heaviest job is not the one failing. Unblocks part of #134 (auto-deploy): with images building in CI and Forgejo's own container registry available on this instance, the registry-based CD path no longer needs anything stood up.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/TeaLeaves#103
No description provided.