DEVX-155: fix: prefer /run/host-docker.sock over inner dockerd
Post-merge / detect-and-configure (push) Canceled after 0s
Post-merge / release-and-maintain (push) Canceled after 0s

The start_docker.py script was checking /var/run/docker.sock first,
which inside CI containers is an inner dockerd (v29.5.3) with data
root on the container's 38G overlay (often 100% full). When
_get_docker_free_bytes() returned 0 (data root path not accessible
from inside container), the script assumed it was the host Docker
with plenty of space and returned True immediately — without trying
the host's rootless Docker socket at /run/host-docker.sock.

Fix: try /run/host-docker.sock FIRST (before /var/run/docker.sock).
The host socket is mounted by the gitea runner config and has access
to the host's full filesystem (455G). Only trust free_bytes == 0
(= data root not accessible from container) for /run/host-docker.sock,
since the host's root dir is genuinely outside the container. For
other sockets (inner dockerd), free_bytes == 0 means the path doesn't
exist inside the container — don't trust it.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
emil
2026-08-14 23:15:03 +02:00
co-authored by Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent 1470cdae27
commit 0e25810b84
2 changed files with 189 additions and 91 deletions
+28 -37
View File
@@ -157,60 +157,41 @@ def start_docker_daemon(timeout: int = DEFAULT_TIMEOUT) -> bool:
Tries sockets in this order, preferring ones with enough disk space:
1. Host socket (``/var/run/docker.sock``) — if it has ≥ ``MIN_FREE_BYTES``
free space, use it immediately.
2. Rootless sockets (``/run/user/*/docker.sock``) — if the host socket
has insufficient space, try rootless sockets which may have access
to the host's full filesystem.
3. Local ``dockerd`` with vfs storage driver — last resort.
1. Host rootless socket (``/run/host-docker.sock``) — mounted by the
gitea runner config, has access to the host's full filesystem
(e.g. 455 GB). Preferred over the inner dockerd.
2. Default socket (``/var/run/docker.sock``) — may be an inner dockerd
started by the CI image (v29.5.3) with data root on the container's
limited overlay (e.g. 38 GB, often 100 % full).
3. Other rootless sockets (``/run/user/*/docker.sock``).
4. Local ``dockerd`` with vfs storage driver — last resort.
Returns ``True`` if Docker is ready, ``False`` if it failed to
start within the timeout.
"""
# Point Docker CLI and Python library to the socket explicitly
os.environ["DOCKER_HOST"] = f"unix://{DOCKER_SOCK}"
# Diagnose socket state
click.echo("--- Docker socket diagnostics ---")
_diagnose_socket()
click.echo("--- End diagnostics ---")
# Check if host Docker is already available
if is_docker_ready():
free_bytes = _get_docker_free_bytes()
free_gb = free_bytes / 1024**3
click.echo(f"Docker daemon already running (free space: {free_gb:.1f} GB)")
if free_bytes >= MIN_FREE_BYTES:
return True
# If free_bytes is 0, the Docker root dir is on the host filesystem
# (not accessible from inside the container). The host Docker has
# access to the full 455G disk — always use it in that case.
if free_bytes == 0:
click.echo("Host Docker root dir not accessible from container, using host Docker")
return True
click.echo(
f"Host Docker has only {free_gb:.1f} GB free "
f"(need ≥ {MIN_FREE_BYTES / 1024**3:.0f} GB), trying rootless sockets..."
)
else:
click.echo("Host Docker not available, trying rootless sockets...")
# Collect candidate rootless sockets
# Collect candidate sockets in priority order.
# The host's rootless Docker socket (mounted at /run/host-docker.sock
# by the gitea runner config) is preferred — it has access to the
# host's full filesystem instead of the container's limited overlay.
candidates: list[str] = []
# The host Docker socket is mounted by the gitea_runner config.
# This is the preferred candidate — it has access to the host's
# full filesystem instead of the container's limited overlay.
if os.path.exists(HOST_DOCKER_SOCK):
candidates.append(HOST_DOCKER_SOCK)
if os.path.exists(DOCKER_SOCK):
candidates.append(DOCKER_SOCK)
if os.path.exists(ROOTLESS_SOCK):
candidates.append(ROOTLESS_SOCK)
for sock in sorted(glob.glob("/run/user/*/docker.sock")):
if sock not in candidates:
candidates.append(sock)
# Try each rootless socket — prefer one with enough free space
# Try each candidate socket — prefer one with enough free space
for sock in candidates:
click.echo(f"Trying rootless socket: {sock}")
click.echo(f"Trying socket: {sock}")
if not _try_socket(sock):
continue
free_bytes = _get_docker_free_bytes()
@@ -219,10 +200,20 @@ def start_docker_daemon(timeout: int = DEFAULT_TIMEOUT) -> bool:
if free_bytes >= MIN_FREE_BYTES:
click.echo(_("Docker daemon already running"))
return True
# If free_bytes is 0, the Docker root dir is on the host filesystem
# (not accessible from inside the container). This is expected for
# the host's rootless Docker — it has the full host disk.
# Only trust this for /run/host-docker.sock (known host socket).
# For other sockets (e.g. inner dockerd), free_bytes == 0 means
# the data root path doesn't exist inside the container — the
# inner dockerd may be using the container's full overlay.
if free_bytes == 0 and sock == HOST_DOCKER_SOCK:
click.echo("Host rootless Docker root dir not accessible from container, using it")
return True
click.echo(f" Insufficient space ({free_gb:.1f} GB), trying next...")
# If we found a working rootless socket but with low space, use it
# as a fallback (better than nothing).
# If we found a working socket but with low space, use the last
# one as a fallback (better than nothing).
for sock in reversed(candidates):
if _try_socket(sock):
click.echo(f"Using low-space fallback: {sock}")