ci: build image daemonless with Kaniko (no Docker)
Some checks failed
Build & Publish / Check (push) Successful in 2m15s
Build & Publish / Build & Publish (push) Failing after 9s

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) <noreply@anthropic.com>
This commit is contained in:
Niels Göttsch 2026-07-16 17:17:09 +02:00
parent 168b674efe
commit 423ddc7870
3 changed files with 67 additions and 31 deletions

View file

@ -67,32 +67,36 @@ jobs:
name: Build & Publish name: Build & Publish
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [check] 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: steps:
- name: Checkout - name: Build and push (Kaniko)
uses: https://github.com/actions/checkout@v4 env:
# Forgejo auto-provides GITHUB_TOKEN; it can push packages for this
- name: Set up Docker Buildx # repo's owner (uberbau) and read this (private-context) git repo.
uses: https://github.com/docker/setup-buildx-action@v3 FORGE_USER: ${{ github.actor }}
FORGE_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Login to the forge container registry # Kaniko reads these to clone the `--context` git URL.
uses: https://github.com/docker/login-action@v3 GIT_USERNAME: ${{ github.actor }}
with: GIT_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
# Forgejo auto-provides GITHUB_TOKEN to the job; it can push packages run: |
# for this repo's owner (uberbau). No external registry / PAT needed. set -eu
registry: ${{ env.REGISTRY }} # Registry auth for the push (docker config.json format).
username: ${{ github.actor }} AUTH=$(printf '%s:%s' "$FORGE_USER" "$FORGE_TOKEN" | base64 | tr -d '\n')
password: ${{ secrets.GITHUB_TOKEN }} mkdir -p /kaniko/.docker
printf '{"auths":{"%s":{"auth":"%s"}}}' "$REGISTRY" "$AUTH" > /kaniko/.docker/config.json
- name: Build and push container # Kaniko clones the context itself (this job has no node for JS actions
uses: https://github.com/docker/build-push-action@v6 # like checkout). `dev` is the moving branch tag; the commit SHA is the
with: # immutable ref the moby Deployment pins by digest.
context: . /kaniko/executor \
push: true --context "git://${REGISTRY}/uberbau/brewlog.git#refs/heads/dev" \
# `dev` is the moving branch tag; the commit SHA is the immutable --dockerfile Dockerfile \
# reference. The moby Deployment pins the image by digest, so bump it --destination "${IMAGE}:${GITHUB_SHA}" \
# there per release (a floating tag never re-rolls the pod on its own). --destination "${IMAGE}:dev"
tags: |
${{ env.IMAGE }}:${{ github.sha }}
${{ env.IMAGE }}:dev
cache-from: type=gha
cache-to: type=gha,mode=max

View file

@ -30,9 +30,10 @@ RUN mkdir -p /usr/local/bin \
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \ # NB: no `RUN --mount=type=cache` here on purpose. The CI image builder is
--mount=type=cache,target=/app/target \ # Kaniko (daemonless, no Docker/BuildKit), which does not support BuildKit cache
cargo build --release --locked \ # mounts — it errors on them. Keep this RUN plain. See README "CI / build".
RUN cargo build --release --locked \
&& mkdir -p /out \ && mkdir -p /out \
&& cp target/release/brewlog /out/brewlog && cp target/release/brewlog /out/brewlog

View file

@ -162,6 +162,37 @@ cargo build # Build
See [CLAUDE.md](CLAUDE.md) for architecture, code patterns, and development conventions. 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,<sha>}`. 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 ## License
[Apache License 2.0](LICENSE) [Apache License 2.0](LICENSE)