ci: build and smoke-test the Image on every PR #20

Merged
piscis merged 7 commits from piscis/forgejo-6-ci-implement into main 2026-09-30 14:38:14 +00:00
Owner

Closes #6

Summary

A ci workflow builds the Image for amd64 and arm64 from the Pin and smoke-tests both, on every PR and on manual dispatch. It never pushes.

ci  (pull_request: opened/synchronize/reopened, workflow_dispatch)
  runs-on: docker, container: docker:cli      # same as probe-runner.yml (#3)
  env IMAGE=forgejo-mcp-ci-<run_id>           # shared dind daemon: per-run tags
  apk add make bash jq nodejs                 # node for actions/checkout; git ships with docker:cli
  actions/checkout
  tonistiigi/binfmt --install arm64           # every run
  cat pin.env; make print-tag
  make build        # Dockerfile checks the Pin's commit against its Upstream Version
  make smoke-test
  always: docker image rm <this run's Image tags>

It adds one change outside the workflow, to scripts/smoke-test.sh, because the runner's daemon is shared:

 workdir=$(mktemp -d)
-name="forgejo-mcp-smoke-$$"
+name="forgejo-mcp-smoke-${workdir##*/}"

Every job container starts fresh with the same PIDs, so $$ was the same across concurrent jobs. They would clash on container and network names, and one job's cleanup could delete another job's stub containers.

How it was verified

Case Run Result
Manual dispatch #3 green: amd64 and arm64 built; PASS: 156 tools listed on both; the run's Image tags removed
PR opened #4 green
Broken Dockerfile (throwaway b3a35fb, reverted) #5 red in make build (apk add git-this-package-does-not-exist), smoke test skipped, cleanup ran
Pin commit ≠ Upstream Version (throwaway 68e52e5, reverted) #6 red in make build: ERROR: Upstream tag v3.2.0 resolves to commit e30bb7e…, but the Pin expects 0000…
Head of this PR #9 green
Two concurrent dispatches on f6e2437 10, 11 red: apk add git → DNS: transient error. BuildKit ran the identical step once for both jobs, so the logs match to the millisecond. It's runner DNS, not a naming collision (#22)
Two concurrent dispatches, re-run 12, 13 green, but every build step came from cache, so this doesn't show that uncached builds work
Two concurrent dispatches, cold cache 14, 15 red, same DNS error
main after the merge, run alone 16, 17 red, same DNS error: builds on the runner can't resolve names (#22)
  • Nothing is pushed: the workflow has no docker login, no --push and no registry credentials. make build only uses --load. After all these runs, GET /api/v1/packages/vicoli-oss?type=container still returns [].
  • The smoke test works against the remote daemon: it uses no bind mounts, and it passed on the dind daemon over tcp://docker_dind:2376.
  • A pushed commit cancels the older run: Forgejo cancelled #7 when the next push replaced it.
  • Correction (added after merge): runs 10 and 11 were not a transient blip. Containers on the dind daemon's default bridge, including BuildKit RUN steps, fall back to Google DNS, which times out from the runner. Every build without a warm cache fails at apk add (probe: run 18). The runner-side fix is #22. The workaround, BUILD_NETWORK=host in CI, is #23: runs 19–23 are green with a cold cache and concurrent jobs.

Merge Danger

Door: two-way

Removing the workflow file turns it off. The smoke-test.sh change only affects container and network names.

Blast Radius: CI

Every PR gets a check that takes about 1–2 minutes. It starts privileged tonistiigi/binfmt containers and removes only its own forgejo-mcp-ci-<run_id> Image tags from the shared daemon. The build cache and shared base images stay.

Closes #6 ## Summary A `ci` workflow builds the Image for amd64 and arm64 from the Pin and smoke-tests both, on every PR and on manual dispatch. It never pushes. ```text ci (pull_request: opened/synchronize/reopened, workflow_dispatch) runs-on: docker, container: docker:cli # same as probe-runner.yml (#3) env IMAGE=forgejo-mcp-ci-<run_id> # shared dind daemon: per-run tags apk add make bash jq nodejs # node for actions/checkout; git ships with docker:cli actions/checkout tonistiigi/binfmt --install arm64 # every run cat pin.env; make print-tag make build # Dockerfile checks the Pin's commit against its Upstream Version make smoke-test always: docker image rm <this run's Image tags> ``` It adds one change outside the workflow, to `scripts/smoke-test.sh`, because the runner's daemon is shared: ```diff workdir=$(mktemp -d) -name="forgejo-mcp-smoke-$$" +name="forgejo-mcp-smoke-${workdir##*/}" ``` Every job container starts fresh with the same PIDs, so `$$` was the same across concurrent jobs. They would clash on container and network names, and one job's cleanup could delete another job's stub containers. ## How it was verified | Case | Run | Result | |---|---|---| | Manual dispatch | [#3](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/3) | green: amd64 and arm64 built; `PASS: 156 tools listed` on both; the run's Image tags removed | | PR opened | [#4](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/4) | green | | Broken Dockerfile (throwaway `b3a35fb`, reverted) | [#5](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/5) | **red** in `make build` (`apk add git-this-package-does-not-exist`), smoke test skipped, cleanup ran | | Pin commit ≠ Upstream Version (throwaway `68e52e5`, reverted) | [#6](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/6) | **red** in `make build`: `ERROR: Upstream tag v3.2.0 resolves to commit e30bb7e…, but the Pin expects 0000…` | | Head of this PR | [#9](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/9) | green | | Two concurrent dispatches on `f6e2437` | [10](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/10), [11](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/11) | **red**: `apk add git` → `DNS: transient error`. BuildKit ran the identical step once for both jobs, so the logs match to the millisecond. It's runner DNS, not a naming collision (#22) | | Two concurrent dispatches, re-run | [12](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/12), [13](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/13) | green, but every build step came from cache, so this doesn't show that uncached builds work | | Two concurrent dispatches, cold cache | [14](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/14), [15](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/15) | **red**, same DNS error | | `main` after the merge, run alone | [16](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/16), [17](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/17) | **red**, same DNS error: builds on the runner can't resolve names (#22) | - **Nothing is pushed:** the workflow has no `docker login`, no `--push` and no registry credentials. `make build` only uses `--load`. After all these runs, `GET /api/v1/packages/vicoli-oss?type=container` still returns `[]`. - **The smoke test works against the remote daemon:** it uses no bind mounts, and it passed on the dind daemon over `tcp://docker_dind:2376`. - **A pushed commit cancels the older run:** Forgejo cancelled [#7](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/7) when the next push replaced it. - **Correction (added after merge):** runs 10 and 11 were not a transient blip. Containers on the dind daemon's default bridge, including BuildKit `RUN` steps, fall back to Google DNS, which times out from the runner. Every build without a warm cache fails at `apk add` (probe: [run 18](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/18)). The runner-side fix is #22. The workaround, `BUILD_NETWORK=host` in CI, is #23: runs [19](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/19)–[23](https://code.vicoli.de/vicoli-oss/docker-forgejo-mcp/actions/runs/23) are green with a cold cache and concurrent jobs. ## Merge Danger **Door:** two-way Removing the workflow file turns it off. The `smoke-test.sh` change only affects container and network names. **Blast Radius:** CI Every PR gets a check that takes about 1–2 minutes. It starts privileged `tonistiigi/binfmt` containers and removes only its own `forgejo-mcp-ci-<run_id>` Image tags from the shared daemon. The build cache and shared base images stay.
ci: build and smoke-test the Image on every PR
All checks were successful
ci / build (pull_request) Successful in 17s
a6149f77bd
Builds the Image for linux/amd64 and linux/arm64 from the Pin and smoke-tests
both through the Makefile, on the docker runner's shared dind daemon. Each run
uses its own IMAGE name and removes its images at the end. Nothing is pushed.

Refs #6
test(ci): THROWAWAY break the Dockerfile
Some checks failed
ci / build (pull_request) Failing after 9s
b3a35fbcaf
Revert "test(ci): THROWAWAY Pin commit that doesn't match the Upstream tag"
Some checks failed
ci / build (pull_request) Has been cancelled
c76070e959
This reverts commit 68e52e599b.
ci: don't install git, docker:cli already ships it
All checks were successful
ci / build (pull_request) Successful in 17s
d940955307
Refs #6
fix(smoke-test): name containers uniquely on a shared daemon
All checks were successful
ci / build (pull_request) Successful in 17s
f6e2437e0d
Concurrent CI jobs each run in a fresh container with the same PIDs, so
forgejo-mcp-smoke-$$ could clash on the runner's shared dind daemon and one
job's cleanup could remove the other's containers. Name them after the
mktemp dir instead.

Also tighten ci.yml comments: only the arm64 smoke test needs QEMU (the
Image cross-compiles, ADR 0001), and the Pin's commit is checked against its
Upstream Version.

Refs #6
piscis merged commit 55e9559181 into main 2026-09-30 14:38:14 +00:00
piscis deleted branch piscis/forgejo-6-ci-implement 2026-09-30 14:38:14 +00:00
Sign in to join this conversation.
No description provided.