Build the container image with ko instead of Docker #2

Merged
nexus merged 3 commits from ci/ko-image into main 2026-07-23 09:44:55 +02:00
Owner

The image job failed with Unable to locate executable file: docker: the
runner's job container has no Docker daemon and not even a docker CLI, so
buildx cannot run there under any configuration.

Switches the image build to ko, which assembles an OCI
image straight from Go source — no daemon, no privileges, no QEMU, and
cross-compilation instead of emulation. linux/amd64 only, as agreed.

Verified locally before pushing: the image runs, carries the version and commit
ldflags, uses the nonroot user, keeps the binary on PATH and defaults to the
run subcommand exactly like the Dockerfile image. Verified in CI on this
branch: test and image are both green.

Two consequences, both handled:

  • The Dockerfile stays as the local build path. make ko-build reproduces what
    CI does, so the two cannot drift apart unnoticed.
  • OCI images have no HEALTHCHECK field, so the compose files and the README
    snippet declare the check themselves. The binary is on PATH in both images,
    so the same command works either way.

Also changes when the image job runs: every branch builds, only main and tags
publish
. Restricting the whole job to main is how the previous breakage got
through — it could not be seen until after the merge.

Still open, and the reason nothing is published yet: the repository secrets
REGISTRY_USER and REGISTRY_TOKEN (a token with write:package). Without
them the job builds and skips the push, so the pipeline stays green.

The image job failed with `Unable to locate executable file: docker`: the runner's job container has no Docker daemon and not even a docker CLI, so buildx cannot run there under any configuration. **Switches the image build to [ko](https://ko.build)**, which assembles an OCI image straight from Go source — no daemon, no privileges, no QEMU, and cross-compilation instead of emulation. `linux/amd64` only, as agreed. Verified locally before pushing: the image runs, carries the version and commit ldflags, uses the nonroot user, keeps the binary on `PATH` and defaults to the `run` subcommand exactly like the Dockerfile image. Verified in CI on this branch: `test` and `image` are both green. Two consequences, both handled: * The Dockerfile stays as the local build path. `make ko-build` reproduces what CI does, so the two cannot drift apart unnoticed. * OCI images have no `HEALTHCHECK` field, so the compose files and the README snippet declare the check themselves. The binary is on `PATH` in both images, so the same command works either way. Also changes when the image job runs: **every branch builds, only main and tags publish**. Restricting the whole job to main is how the previous breakage got through — it could not be seen until after the merge. Still open, and the reason nothing is published yet: the repository secrets `REGISTRY_USER` and `REGISTRY_TOKEN` (a token with `write:package`). Without them the job builds and skips the push, so the pipeline stays green.
Build the image with ko, because the runner has no Docker at all
Some checks failed
CI / image (push) Has been cancelled
CI / test (push) Has been cancelled
40a1b661f0
The image job died on 'Unable to locate executable file: docker' — the runner's
job container has no Docker daemon and not even a docker CLI, so buildx cannot
run there under any configuration.

ko assembles an OCI image straight from Go source: no daemon, no privileges,
no QEMU. Verified locally end to end — the image runs, carries the version and
commit ldflags, uses the nonroot user and defaults to the run subcommand just
like the Dockerfile one.

Two consequences, both handled:
  * The Dockerfile stays as the local build path. 'make ko-build' reproduces
    what CI does, so the two cannot drift silently.
  * OCI images have no HEALTHCHECK field, so the compose files declare the
    check themselves. The binary is on PATH in both images, so the same
    command works either way.

Architecture is amd64 only, as agreed.
Build the image on every branch, publish only from main
All checks were successful
CI / test (push) Successful in 1m34s
CI / image (push) Successful in 2m50s
CI / test (pull_request) Successful in 3m11s
CI / image (pull_request) Successful in 3m0s
1f59182039
Restricting the whole job to main meant a broken image build could only be
discovered after merging — which is exactly how the last one slipped through.
The build now runs everywhere and the publish decision moved into the
authentication step.
Default the registry user to the repository owner
All checks were successful
CI / test (push) Successful in 3m2s
CI / test (pull_request) Successful in 2m56s
CI / image (push) Successful in 3m9s
CI / image (pull_request) Successful in 3m21s
07ecbf5e7c
A user name is not a secret and asking for a second repository secret just to
repeat the owner is friction. REGISTRY_USER now only exists to publish as
somebody else; REGISTRY_TOKEN alone is enough.
nexus merged commit 6a1ac8461f into main 2026-07-23 09:44:55 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
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
nexus/WifiPoolToMQTT!2
No description provided.