From 5d65ca06c1bbdbb1977b54f351b108af3b6aaacf Mon Sep 17 00:00:00 2001 From: Lorem Dev Date: Thu, 13 Aug 2026 01:51:36 +0200 Subject: [PATCH] ci: build the image on main, not only on a tag The image was built by `release.yml` and nowhere else, so a change that broke the Dockerfile was found by the release that was meant to publish it -- at the one moment when the fix costs a second tag. This asks the same question on every push to `main` and on every pull request aiming at it, which is where a release goes through. Build only. Nothing is pushed and nothing logs in: the question is whether the image builds, not whether the branch can publish, and a job needing credentials could not run on a fork. One platform, the runner's own, because the other means QEMU and turns a two-minute compile into most of an hour -- the release builds each natively and stages the binaries, which is a different path and is checked by the release itself. From source with nothing staged, which is the branch a developer hits with `docker build .` on a checkout: the compile *and* the dashboard build happen inside the image, and the release never runs that. `lfs: true`, because `assets/favicon.ico` is an LFS object the frontend's prebuild copies -- without it vite ships a pointer file as the favicon. Cached through `type=gha`, or every push to main pays for a release-profile compile of the whole workspace. No CHANGES.md entry: this changes nothing an operator or a client can observe. --- .agents/skills/pre-release-check/SKILL.md | 5 +++ .github/workflows/ci.yml | 55 +++++++++++++++++++++++ docs/development/index.md | 8 ++++ 3 files changed, 68 insertions(+) diff --git a/.agents/skills/pre-release-check/SKILL.md b/.agents/skills/pre-release-check/SKILL.md index ebec02d..e49efbd 100644 --- a/.agents/skills/pre-release-check/SKILL.md +++ b/.agents/skills/pre-release-check/SKILL.md @@ -37,6 +37,11 @@ it still builds: make e2e # the browser suite, which the frontend gate leaves out ``` +The image itself is built by CI on every push to `main` and on every pull request +aiming at it, from source and without pushing, so a broken Dockerfile is not +something this skill has to discover. Read that job's result rather than building +it again by hand. + Then confirm the release will not ship a page-less binary. The workflow greps each archive's binary for the configuration element before publishing -- read that step and check it is still there, since it is the only thing standing between a missing diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e29c4d5..f46271f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -270,3 +270,58 @@ jobs: name: playwright-report path: frontend/playwright-report retention-days: 7 + + # The image, built and thrown away. + # + # It used to be built only by `release.yml`, on a tag -- so a change that broke + # the Dockerfile was found by the release that was supposed to publish it, at + # the one moment when the fix costs a second tag. This job answers the same + # question earlier and for free. + # + # Three deliberate limits: + # + # * **Nothing is pushed and nothing logs in.** The question is whether the + # image builds, not whether this branch can publish; a job that needed + # credentials could not run on a fork. + # * **One platform, the runner's own.** The other architecture means QEMU, + # which turns a two-minute compile into most of an hour -- see the Dockerfile + # header. The release builds each natively and stages the binaries, which is + # a different path and is checked by the release. + # * **From source, with nothing staged.** That is the branch a developer hits + # with `docker build .` on a checkout: the compile *and* the dashboard build + # happen inside the image. The release never runs it, so nothing else does. + image: + name: Image builds + # A push to main, and a pull request aiming at it -- which is what a release + # goes through, and the point is to know before the tag rather than after. + if: >- + (github.event_name == 'push' && github.ref == 'refs/heads/main') || + (github.event_name == 'pull_request' && github.base_ref == 'main') + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + # `assets/favicon.ico` is an LFS object and the frontend's prebuild step + # copies it into `public/`. Without this the build stage gets a pointer + # file, and vite ships a hundred bytes of ASCII as the favicon. + lfs: true + + - name: Set up buildx + uses: docker/setup-buildx-action@v4 + + # Cached because otherwise every push to main pays for a release-profile + # compile of the whole workspace inside the image. + - name: Build the image + uses: docker/build-push-action@v7 + with: + context: . + push: false + # Not loaded into the daemon either: nothing here runs it, and a load + # is a copy of a 30 MB image nobody reads. + load: false + tags: doppel:ci + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/docs/development/index.md b/docs/development/index.md index de989d6..b5e4336 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -22,6 +22,14 @@ Capture its output rather than recalling it: { cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test; } 2>&1 | tee /tmp/gate.txt ``` +### What CI adds + +The same gate, plus two things a laptop leaves out: the browser suite, and the +container image. The image is built on every push to `main` and on every pull +request aiming at it -- from source, with nothing staged, and never pushed. It used +to be built only by the release workflow on a tag, which meant a broken Dockerfile +surfaced at the one moment when the fix costs a second tag. + ## Tests Unit tests live beside the code. Integration tests in