Skip to content

Commit 9ee50cd

Browse files
fix(docker): let the agent sandbox reach the Docker daemon
The application server and worker mount /var/run/docker.sock to run agent sandboxes, but the buildpack image runs unprivileged and the socket is root:docker 0660. Every sandbox start was refused: SandboxReconciler: Failed to scan for orphaned containers: java.net.BindException: Permission denied Staging logged 1469 such lines in 24 hours and its agent_job table is empty: no practice review has ever run there, and none could. The same applies to every Compose deployment, self-hosted ones included. Both services now join the host's Docker group. There is no portable group id, so DOCKER_GROUP_ID is documented rather than guessed; an unset value leaves the deployment exactly where it is today. Verified on the staging host: the same unprivileged uid is refused by the daemon without the group and reaches it with the group added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EZADQeSx6zQNNqsdu7AAqZ
1 parent 7b03a84 commit 9ee50cd

5 files changed

Lines changed: 39 additions & 0 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"hephaestus": minor
3+
---
4+
5+
Practice reviews can now actually start. The application server and worker run unprivileged, so every attempt to launch an agent sandbox was refused by the Docker socket with a permission error and no review ever ran. They now join the host's Docker group.
6+
7+
**Operators:** set `DOCKER_GROUP_ID` to the group id that owns `/var/run/docker.sock` on your host — `getent group docker | cut -d: -f3` prints it. There is no portable default, so a deployment that leaves it unset keeps failing the same way it does today.

docker/.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,13 @@ GITLAB_WORKSPACE_CREATION=false
238238
# GIT_CHECKOUT_ENABLED enabled together. The agent job queue runs on PostgreSQL, not NATS —
239239
# NATS_ENABLED below is unrelated to practice review; it only gates webhook-driven sync ingest.
240240

241+
# Supplementary group the application server and worker join so they can reach the Docker socket
242+
# the agent sandbox runs containers through. The image runs unprivileged and the socket is
243+
# root:docker 0660, so without this every sandbox start fails with "permission denied" and no
244+
# practice review can run. There is no portable default — read the host's group id with
245+
# `getent group docker | cut -d: -f3`.
246+
# DOCKER_GROUP_ID=999
247+
241248
# Docker daemon endpoint (default: local socket)
242249
# SANDBOX_DOCKER_HOST=unix:///var/run/docker.sock
243250

docker/compose.app.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ services:
275275
release-pin-fetcher:
276276
condition: service_completed_successfully
277277
restart: unless-stopped
278+
# The image runs unprivileged (the buildpack's `cnb` user) while /var/run/docker.sock is
279+
# root:docker 0660, so the sandbox runtime cannot reach the daemon without joining the host's
280+
# docker group. There is no portable value: read the host's with
281+
# `getent group docker | cut -d: -f3` and set DOCKER_GROUP_ID to it.
282+
group_add:
283+
- "${DOCKER_GROUP_ID:-999}"
278284
volumes:
279285
- /var/run/docker.sock:/var/run/docker.sock
280286
- git-repos:/data/git-repos
@@ -404,6 +410,12 @@ services:
404410
release-pin-fetcher:
405411
condition: service_completed_successfully
406412
restart: unless-stopped
413+
# The image runs unprivileged (the buildpack's `cnb` user) while /var/run/docker.sock is
414+
# root:docker 0660, so the sandbox runtime cannot reach the daemon without joining the host's
415+
# docker group. There is no portable value: read the host's with
416+
# `getent group docker | cut -d: -f3` and set DOCKER_GROUP_ID to it.
417+
group_add:
418+
- "${DOCKER_GROUP_ID:-999}"
407419
volumes:
408420
- /var/run/docker.sock:/var/run/docker.sock
409421
- git-repos:/data/git-repos

docker/preview/.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,10 @@ TANSTACK_DEVTOOLS_ENABLED=true
110110
# rollout testing are supported. Enable one workspace's review model binding and trigger in the UI.
111111

112112
# HEPHAESTUS_AGENT_IMAGE_REFERENCE=ghcr.io/ls1intum/hephaestus/agent-pi@sha256:<digest> # preview override only
113+
114+
# Supplementary group the application server and worker join so they can reach the Docker socket
115+
# the agent sandbox runs containers through. The image runs unprivileged and the socket is
116+
# root:docker 0660, so without this every sandbox start fails with "permission denied" and no
117+
# practice review can run. There is no portable default — read the host's group id with
118+
# `getent group docker | cut -d: -f3`.
119+
# DOCKER_GROUP_ID=999

docker/preview/compose.app.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,12 @@ services:
347347
WEBHOOK_EXTERNAL_URL: ${WEBHOOK_EXTERNAL_URL:-https://staging.hephaestus.aet.cit.tum.de}
348348
THC_PORT: "8080"
349349
THC_PATH: /actuator/health/liveness
350+
# The image runs unprivileged (the buildpack's `cnb` user) while /var/run/docker.sock is
351+
# root:docker 0660, so the sandbox runtime cannot reach the daemon without joining the host's
352+
# docker group. There is no portable value: read the host's with
353+
# `getent group docker | cut -d: -f3` and set DOCKER_GROUP_ID to it.
354+
group_add:
355+
- "${DOCKER_GROUP_ID:-999}"
350356
volumes:
351357
- /var/run/docker.sock:/var/run/docker.sock
352358
- git-repos:/data/git-repos

0 commit comments

Comments
 (0)