Fix job-container docker access via a shared unix socket, not TCP

The previous fix (docker_host: tcp://dind:2375) was wrong: act_runner
never passes DOCKER_HOST into job containers as an env var. It always
bind-mounts container.docker_host (read from ITS OWN filesystem) into
every job container as /var/run/docker.sock, and only recognizes
unix:// (or bare) paths -- a tcp:// value is silently treated as
unknown and falls back to a /var/run/docker.sock that doesn't exist in
the runner container, which is exactly the "no such file or directory"
error.

Expose dind's daemon over an additional unix socket on a volume shared
with the runner (dind-sock), and point docker_host at that shared path
so the bind-mount act_runner performs actually resolves to dind's real
socket. Verified by replicating act_runner's exact bind-mount pattern
against the running stack: `docker -H tcp://dind:2375 run -v
/dind-sock/docker.sock:/var/run/docker.sock ...` successfully ran
`docker info` and `docker build` inside a nested container.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFsukF6FzwBxJtRGzbt28F
This commit is contained in:
2026-07-03 16:57:33 +07:00
co-authored by Claude Sonnet 5
parent 88d5c6d2f5
commit 37aca003ee
2 changed files with 28 additions and 10 deletions
+11 -8
View File
@@ -32,18 +32,21 @@ container:
# Talk to the DinD daemon, not the host.
network: ""
privileged: false
# Job containers run *inside* the dind engine, on its own internal bridge network,
# so the "dind" hostname (only known on the outer compose network) doesn't resolve
# there. host-gateway maps it to dind's own bridge-gateway IP, where its dockerd
# listens (started with --host=tcp://0.0.0.0:2375), so job steps can reach it.
options: "--add-host=dind:host-gateway"
options: ""
workdir_parent: ""
# Allow workflows to mount caches from the DinD volume.
valid_volumes:
- "**"
# Pass DOCKER_HOST into job/service containers so steps that call `docker` directly
# (e.g. `docker build`) can reach the DinD daemon. Paired with the --add-host above.
docker_host: "tcp://dind:2375"
# act_runner bind-mounts this path (from its own container's filesystem) into
# every job/service container as /var/run/docker.sock -- it does not pass
# DOCKER_HOST through as an env var, and only unix:// (or bare) paths are
# recognized (see getDockerDaemonSocketMountPath in act_runner's source; a
# "tcp://" value is silently treated as unknown and falls back to the
# nonexistent default path). This is dind's docker.sock exposed over the
# dind-sock volume shared between the dind and runner services in
# docker-compose.yml, so job steps that call `docker` directly (e.g. `docker
# build`) reach the real DinD daemon.
docker_host: "unix:///dind-sock/docker.sock"
force_pull: false
host:
+17 -2
View File
@@ -6,7 +6,15 @@ services:
container_name: gitea-runner-dind
privileged: true
restart: unless-stopped
command: ["dockerd", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--tls=false"]
# The third --host exposes the daemon over a unix socket on a volume shared
# with the runner (dind-sock), *in addition to* its own /var/run/docker.sock.
# act_runner's job-container DooD wiring only understands unix:// (or bare)
# socket paths -- it bind-mounts that path from ITS OWN filesystem into every
# job container as /var/run/docker.sock, it does not pass DOCKER_HOST through
# as an env var. A bare "tcp://..." isn't recognized and silently falls back
# to /var/run/docker.sock, which doesn't exist in the runner container -- so
# this shared socket is what makes `docker build` etc. work inside jobs.
command: ["dockerd", "--host=tcp://0.0.0.0:2375", "--host=unix:///var/run/docker.sock", "--host=unix:///dind-sock/docker.sock", "--tls=false"]
environment:
DOCKER_TLS_CERTDIR: ""
networks:
@@ -14,6 +22,8 @@ services:
volumes:
# Persist pulled job images + docker layer cache => fast Rust rebuilds.
- dind-storage:/var/lib/docker
# Shared with the runner container; see the --host comment above.
- dind-sock:/dind-sock
healthcheck:
test: ["CMD", "docker", "info"]
interval: 5s
@@ -35,13 +45,17 @@ services:
GITEA_RUNNER_REGISTRATION_TOKEN: ${GITEA_RUNNER_REGISTRATION_TOKEN}
GITEA_RUNNER_NAME: ${GITEA_RUNNER_NAME}
GITEA_RUNNER_LABELS: ""
# Point the runner at the DinD daemon.
# Point the runner at the DinD daemon. Overridden at startup by act_runner
# itself to config.yaml's container.docker_host (the unix socket below),
# since our labels require docker; kept here as a documented fallback.
DOCKER_HOST: tcp://dind:2375
CONFIG_FILE: /config.yaml
volumes:
- ./config.yaml:/config.yaml:ro
# Persist runner registration so it survives restarts.
- runner-data:/data
# Shared with dind; see the --host comment on the dind service.
- dind-sock:/dind-sock
networks:
runner:
@@ -49,3 +63,4 @@ networks:
volumes:
dind-storage:
runner-data:
dind-sock: