From 423ddc7870518c31f1622aeb07bd889253814a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20G=C3=B6ttsch?= Date: Thu, 16 Jul 2026 17:17:09 +0200 Subject: [PATCH] ci: build image daemonless with Kaniko (no Docker) Replace the docker buildx / build-push-action build job with Kaniko running as the job container: it builds the Dockerfile from the git context and pushes to the forge registry, with no Docker daemon, no buildx, and no privileged DinD. Kaniko cannot handle BuildKit `RUN --mount=type=cache`, so drop the two cache mounts on the cargo build layer (plain `cargo build` now). Document the pipeline and this gotcha (plus the node20-only runner and full-github-URL action rules) in the README so it isn't reintroduced. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/deploy.yml | 60 +++++++++++++++++++----------------- Dockerfile | 7 +++-- README.md | 31 +++++++++++++++++++ 3 files changed, 67 insertions(+), 31 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 9995cfa..db5db21 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -67,32 +67,36 @@ jobs: name: Build & Publish runs-on: ubuntu-latest needs: [check] + # Build the image DAEMONLESS with Kaniko — no Docker, no buildx, no + # privileged DinD. The job runs *inside* the Kaniko executor image, which + # builds the Dockerfile and pushes straight to the forge registry. + container: + image: gcr.io/kaniko-project/executor:v1.24.0-debug + defaults: + run: + # The kaniko -debug image is busybox-only (no bash). + shell: sh steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Set up Docker Buildx - uses: https://github.com/docker/setup-buildx-action@v3 - - - name: Login to the forge container registry - uses: https://github.com/docker/login-action@v3 - with: - # Forgejo auto-provides GITHUB_TOKEN to the job; it can push packages - # for this repo's owner (uberbau). No external registry / PAT needed. - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push container - uses: https://github.com/docker/build-push-action@v6 - with: - context: . - push: true - # `dev` is the moving branch tag; the commit SHA is the immutable - # reference. The moby Deployment pins the image by digest, so bump it - # there per release (a floating tag never re-rolls the pod on its own). - tags: | - ${{ env.IMAGE }}:${{ github.sha }} - ${{ env.IMAGE }}:dev - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Build and push (Kaniko) + env: + # Forgejo auto-provides GITHUB_TOKEN; it can push packages for this + # repo's owner (uberbau) and read this (private-context) git repo. + FORGE_USER: ${{ github.actor }} + FORGE_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Kaniko reads these to clone the `--context` git URL. + GIT_USERNAME: ${{ github.actor }} + GIT_PASSWORD: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eu + # Registry auth for the push (docker config.json format). + AUTH=$(printf '%s:%s' "$FORGE_USER" "$FORGE_TOKEN" | base64 | tr -d '\n') + mkdir -p /kaniko/.docker + printf '{"auths":{"%s":{"auth":"%s"}}}' "$REGISTRY" "$AUTH" > /kaniko/.docker/config.json + # Kaniko clones the context itself (this job has no node for JS actions + # like checkout). `dev` is the moving branch tag; the commit SHA is the + # immutable ref the moby Deployment pins by digest. + /kaniko/executor \ + --context "git://${REGISTRY}/uberbau/brewlog.git#refs/heads/dev" \ + --dockerfile Dockerfile \ + --destination "${IMAGE}:${GITHUB_SHA}" \ + --destination "${IMAGE}:dev" diff --git a/Dockerfile b/Dockerfile index 94b7971..e317703 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,9 +30,10 @@ RUN mkdir -p /usr/local/bin \ WORKDIR /app COPY . . -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/app/target \ - cargo build --release --locked \ +# NB: no `RUN --mount=type=cache` here on purpose. The CI image builder is +# Kaniko (daemonless, no Docker/BuildKit), which does not support BuildKit cache +# mounts — it errors on them. Keep this RUN plain. See README "CI / build". +RUN cargo build --release --locked \ && mkdir -p /out \ && cp target/release/brewlog /out/brewlog diff --git a/README.md b/README.md index 88babdf..afd3f08 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,37 @@ cargo build # Build See [CLAUDE.md](CLAUDE.md) for architecture, code patterns, and development conventions. +## CI / build pipeline (this fork) + +This fork lives on the **zo** Forgejo forge +(`git.ziemlichoptimal.de/uberbau/brewlog`, default branch **`dev`**) and is +deployed to the **moby** homelab via Flux GitOps. `.github/workflows/deploy.yml` +runs on every push to `dev`: it lints/tests (`check`), then builds and publishes +the container image to the forge's own registry as +`git.ziemlichoptimal.de/uberbau/brewlog:{dev,}`. The moby deployment pins +that image by digest and bumps it per release. + +The build is **daemonless — it does not use Docker**. It runs +[Kaniko](https://github.com/GoogleContainerTools/kaniko) as the job container, +building the `Dockerfile` (context pulled straight from git) and pushing to the +registry. No `docker`, no buildx, no privileged Docker-in-Docker. + +### Gotchas (learned the hard way) + +- **Kaniko does not support BuildKit `RUN --mount=type=cache`.** The build fails + on it. Keep every `RUN` in the `Dockerfile` plain — no BuildKit cache mounts. + (This is why the Rust build layer is a plain `cargo build`; we have been bitten + by this before, so do not "re-add caching" to the Dockerfile.) +- **The forge's Actions runner is node20-only.** Pin JS actions to their node20 + generation (`actions/checkout@v4`, `actions/cache@v4`, and if you reintroduce + docker actions, `@v3`/`@v6`) — the newer `@v5/@v6/@v7` releases declare + `runs.using: node24`, which the runner rejects (`must be one of [… node20 …]`). +- **Reference actions by full `github.com` URL** + (`uses: https://github.com/owner/repo@ref`). The runner's default action + mirror (`code.forgejo.org`) is incomplete/flaky and aborts clones mid-run. +- The `check` job installs only Rust via mise (`install_args: rust`); the full + `mise.toml` toolchain pulls tools from the GitHub API, which 401s on the runner. + ## License [Apache License 2.0](LICENSE)