From 5ddbca49d356bcacf74a7f183d53530d088bd7e2 Mon Sep 17 00:00:00 2001 From: Jaap-Jan de Wit | DodoTech Date: Sat, 1 Aug 2026 15:14:28 +0200 Subject: [PATCH] Give the image job a docker client to go with the daemon it already had MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exit 127, `docker: command not found`, from the build step of the image job. The daemon was never the problem and never missing: Testcontainers speaks to /var/run/docker.sock from a .NET library, so every integration suite in the build job had been starting PostgreSQL, Keycloak and an sshd on this runner while `docker` was not a command on it at all. Having a socket and having a client are two different things to have, and this runner had one. It failed late for the same reason it was easy to miss. Node, git, the SDK and the tags all came up fine, so the job looked healthy right until the line that actually needed the binary. The client only. There is a daemon answering on that socket already — installing an engine would start a second one beside the one in use, which is a worse outcome than the error. Verified by running this step's own script in an Alpine container with the socket mounted: it installs docker-cli, the client then reports server 29.6.2 across the socket, and the API image builds to completion from inside that container with the repository as its context. Which is as close to the runner as this can be checked without being it. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e970f50..8f67d98 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -337,6 +337,40 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # The daemon and the client are two different things to have, and this runner had only + # one of them. Testcontainers reaches Docker straight over /var/run/docker.sock from a + # .NET library, so every integration suite in the build job passed while `docker` was + # not a command here at all — which surfaced as exit 127 from the build step below, + # after the tags had been worked out and everything looked healthy. + # + # Only the CLI. The socket is already there and a daemon is already answering on it; + # installing an engine would start a second one beside the one being used. + - name: ensure the docker cli + run: | + set -eu + SUDO="" + [ "$(id -u)" -eq 0 ] || SUDO="sudo" + + if command -v docker >/dev/null 2>&1; then + docker --version + exit 0 + fi + + echo "Installing the docker cli" + if command -v apk >/dev/null 2>&1; then + $SUDO apk add --no-cache docker-cli + elif command -v apt-get >/dev/null 2>&1; then + $SUDO apt-get update -qq + $SUDO apt-get install -y --no-install-recommends docker.io + elif command -v dnf >/dev/null 2>&1; then + $SUDO dnf install -y docker-cli + else + echo "No apt-get, apk or dnf here, so the docker client cannot be installed from" >&2 + echo "inside the job. Add it to the runner's image." >&2 + exit 1 + fi + docker --version + # No docker/* actions here, deliberately. The build is single-architecture, so it # needs the daemon this runner already has for the Testcontainers suites and nothing # else — no buildx plugin, no QEMU, and no third-party action whose SHA has to be