ci: build against a dind sidecar instead of the host socket

The host docker socket wasn't mounted consistently across act_runners,
so which runner picked up the push was the difference between a clean
build and "unable to connect to docker." Switch to a dockerd service
pinned to the same bridge network as the job; the CLI now talks to
it over TCP via DOCKER_HOST=tcp://dockerd:2375. Every runner has a
daemon, every time — no more re-runs for the scheduling lottery.
This commit is contained in:
Graham McIntire 2026-04-19 12:24:41 -05:00
parent c5436279ed
commit 2a6f14129a
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59

View file

@ -18,6 +18,22 @@ jobs:
name: Build and Push Docker Image
runs-on: ubuntu-22.04
# Runs a dockerd sidecar on the same bridge network as the job
# container so the build doesn't depend on the host's docker socket
# being mounted. Some act_runners don't mount it and there was no
# way to tell at schedule time which runner would win — leading to
# random "unable to connect to docker" failures that went away on a
# re-run. With dind every runner has a daemon, every time.
services:
dockerd:
image: docker:27-dind
options: --privileged
env:
DOCKER_TLS_CERTDIR: ""
env:
DOCKER_HOST: tcp://dockerd:2375
steps:
- name: Checkout code
uses: https://github.com/actions/checkout@v4
@ -26,8 +42,8 @@ jobs:
run: |
# Dodge apt entirely — the runner image's bookworm repos have been
# failing GPG verification with "At least one invalid signature".
# The static Docker client binary talks to the host's daemon socket
# that act_runner mounts into the job container. buildx is needed
# The static Docker client binary talks to the dind sidecar via
# the DOCKER_HOST env var set at the job level. buildx is needed
# separately because the Dockerfile uses `--mount=type=cache`,
# which is a BuildKit-only feature.
curl -fsSL -o /tmp/docker.tgz \
@ -44,28 +60,22 @@ jobs:
docker --version
docker buildx version
- name: Wait for Docker daemon
- name: Wait for dockerd sidecar
run: |
# act_runner mounts the host docker socket into the job, but
# the daemon side sometimes takes a moment to answer. Poll
# `docker info` until it responds so the login/build steps
# below don't race the socket and fail with "unable to
# connect to docker".
sock="${DOCKER_HOST:-unix:///var/run/docker.sock}"
echo "Using DOCKER_HOST=${sock}"
# dind takes ~5-15s to finish booting inside the service
# container; poll until the TCP endpoint answers.
echo "Using DOCKER_HOST=${DOCKER_HOST}"
for i in $(seq 1 30); do
for i in $(seq 1 60); do
if docker info >/dev/null 2>&1; then
echo "Docker daemon reachable after ${i}s"
echo "dockerd reachable after ${i}s"
exit 0
fi
echo "Waiting for Docker daemon... ($i/30)"
echo "Waiting for dockerd... ($i/60)"
sleep 1
done
echo "Docker daemon never became reachable — diagnostics:" >&2
echo "--- ls -l /var/run/docker.sock ---" >&2
ls -l /var/run/docker.sock 2>&1 || true
echo "dockerd never became reachable — diagnostics:" >&2
echo "--- env | grep -i docker ---" >&2
env | grep -i docker 2>&1 || true
echo "--- docker info (verbose) ---" >&2