Skip to content

Commit 4b6dddc

Browse files
committed
fix(api): isolate docker runtime from host socket
1 parent 279de76 commit 4b6dddc

27 files changed

Lines changed: 890 additions & 636 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ docker-git apply-all
5555
docker-git apply-all --active
5656
```
5757

58-
- `apply` применяет конфиг к одному проекту. `--no-up` только обновляет файлы без `docker compose up`. В текущем API-only host mode команда ещё недоступна.
58+
- `apply` применяет конфиг к одному проекту. `--no-up` только обновляет файлы без `docker compose up`.
5959
- `apply-all` применяет конфиг ко всем проектам. `--active` только к запущенным контейнерам.
6060

6161

docker-compose.api.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ services:
88
container_name: ${DOCKER_GIT_API_CONTAINER_NAME:-docker-git-api}
99
environment:
1010
DOCKER_GIT_API_PORT: ${DOCKER_GIT_API_PORT:-3334}
11+
DOCKER_GIT_DOCKER_RUNTIME: ${DOCKER_GIT_DOCKER_RUNTIME:-isolated}
12+
DOCKER_HOST: ${DOCKER_GIT_CONTROLLER_DOCKER_HOST:-unix:///var/run/docker.sock}
13+
DOCKER_GIT_DOCKERD_TCP_HOST: ${DOCKER_GIT_DOCKERD_TCP_HOST:-tcp://0.0.0.0:2375}
14+
DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE: ${DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE:-host}
15+
DOCKER_GIT_PROJECT_DOCKER_HOST: ${DOCKER_GIT_PROJECT_DOCKER_HOST:-tcp://host.docker.internal:2375}
16+
DOCKER_GIT_PROJECT_SSH_BIND_HOST: ${DOCKER_GIT_PROJECT_SSH_BIND_HOST:-0.0.0.0}
1117
DOCKER_GIT_PROJECTS_ROOT: ${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git}
1218
DOCKER_GIT_PROJECTS_ROOT_VOLUME: ${DOCKER_GIT_PROJECTS_ROOT_VOLUME:-docker-git-projects}
1319
DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN: ${DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN:-}
@@ -25,10 +31,15 @@ services:
2531
- 8.8.4.4
2632
- 1.1.1.1
2733
volumes:
28-
- /var/run/docker.sock:/var/run/docker.sock
2934
- docker_git_projects:${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git}
35+
- docker_git_docker_data:/var/lib/docker
36+
privileged: true
37+
cgroup: host
38+
init: true
3039
restart: unless-stopped
3140

3241
volumes:
3342
docker_git_projects:
3443
name: ${DOCKER_GIT_PROJECTS_ROOT_VOLUME:-docker-git-projects}
44+
docker_git_docker_data:
45+
name: ${DOCKER_GIT_DOCKER_DATA_VOLUME:-docker-git-docker-data}

docker-compose.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ services:
1010
environment:
1111
DOCKER_GIT_API_PORT: ${DOCKER_GIT_API_PORT:-3334}
1212
DOCKER_GIT_CONTROLLER_REV: ${DOCKER_GIT_CONTROLLER_REV:-unknown}
13+
DOCKER_GIT_DOCKER_RUNTIME: ${DOCKER_GIT_DOCKER_RUNTIME:-isolated}
14+
DOCKER_HOST: ${DOCKER_GIT_CONTROLLER_DOCKER_HOST:-unix:///var/run/docker.sock}
15+
DOCKER_GIT_DOCKERD_TCP_HOST: ${DOCKER_GIT_DOCKERD_TCP_HOST:-tcp://0.0.0.0:2375}
16+
DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE: ${DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE:-host}
17+
DOCKER_GIT_PROJECT_DOCKER_HOST: ${DOCKER_GIT_PROJECT_DOCKER_HOST:-tcp://host.docker.internal:2375}
18+
DOCKER_GIT_PROJECT_SSH_BIND_HOST: ${DOCKER_GIT_PROJECT_SSH_BIND_HOST:-0.0.0.0}
1319
DOCKER_GIT_PROJECTS_ROOT: ${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git}
1420
DOCKER_GIT_PROJECTS_ROOT_VOLUME: ${DOCKER_GIT_PROJECTS_ROOT_VOLUME:-docker-git-projects}
1521
DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN: ${DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN:-}
@@ -27,10 +33,15 @@ services:
2733
- 8.8.4.4
2834
- 1.1.1.1
2935
volumes:
30-
- /var/run/docker.sock:/var/run/docker.sock
3136
- docker_git_projects:${DOCKER_GIT_PROJECTS_ROOT:-/home/dev/.docker-git}
37+
- docker_git_docker_data:/var/lib/docker
38+
privileged: true
39+
cgroup: host
40+
init: true
3241
restart: unless-stopped
3342

3443
volumes:
3544
docker_git_projects:
3645
name: ${DOCKER_GIT_PROJECTS_ROOT_VOLUME:-docker-git-projects}
46+
docker_git_docker_data:
47+
name: ${DOCKER_GIT_DOCKER_DATA_VOLUME:-docker-git-docker-data}

packages/api/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ RUN bun run --cwd packages/lib build
5959
RUN bun run --cwd packages/api build
6060

6161
ENV DOCKER_GIT_API_PORT=3334
62+
ENV DOCKER_GIT_DOCKER_RUNTIME=isolated
63+
ENV DOCKER_HOST=unix:///var/run/docker.sock
6264
EXPOSE 3334
6365

64-
CMD ["bun", "packages/api/dist/src/main.js"]
66+
CMD ["bash", "packages/api/scripts/start-controller.sh"]

packages/api/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ HTTP API for docker-git orchestration (projects, agents, logs/events, federation
55
This is now the intended controller plane:
66
- the API runs inside `docker-git-api`
77
- `.docker-git` state lives in the Docker volume `docker-git-projects`
8-
- the API talks to Docker through `/var/run/docker.sock`
8+
- the API starts an isolated Docker daemon inside the controller by default
99
- child project containers no longer depend on host bind mounts for bootstrap auth/env
10+
- the host `/var/run/docker.sock` is not mounted into the controller or project containers
1011

1112
## UI wrapper
1213

@@ -41,6 +42,12 @@ Optional env:
4142

4243
- `DOCKER_GIT_API_BIND_HOST` (default: `127.0.0.1`)
4344
- `DOCKER_GIT_API_PORT` (default: `3334`)
45+
- `DOCKER_GIT_DOCKER_RUNTIME` (default: `isolated`; starts a managed Docker daemon in `docker-git-api`)
46+
- `DOCKER_GIT_CONTROLLER_DOCKER_HOST` (default: `unix:///var/run/docker.sock`; socket path inside the controller)
47+
- `DOCKER_GIT_DOCKERD_TCP_HOST` (default: `tcp://0.0.0.0:2375`; reachable only inside Docker networks unless explicitly published)
48+
- `DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE` (default: `host`; keeps nested project containers compatible with cgroup v2 DinD)
49+
- `DOCKER_GIT_PROJECT_DOCKER_HOST` (default: `tcp://host.docker.internal:2375`; lets project containers use the isolated daemon)
50+
- `DOCKER_GIT_PROJECT_SSH_BIND_HOST` (default: `0.0.0.0` in controller mode; project SSH binds inside the isolated controller runtime)
4451
- `DOCKER_GIT_PROJECTS_ROOT` (container path, default: `/home/dev/.docker-git`)
4552
- `DOCKER_GIT_PROJECTS_ROOT_VOLUME` (Docker volume name for controller state, default: `docker-git-projects`)
4653
- `DOCKER_GIT_FEDERATION_PUBLIC_ORIGIN` (optional public ActivityPub origin)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
runtime="${DOCKER_GIT_DOCKER_RUNTIME:-isolated}"
5+
docker_host="${DOCKER_HOST:-unix:///var/run/docker.sock}"
6+
dockerd_pid=""
7+
8+
cleanup() {
9+
if [[ -n "$dockerd_pid" ]] && kill -0 "$dockerd_pid" >/dev/null 2>&1; then
10+
kill "$dockerd_pid" >/dev/null 2>&1 || true
11+
wait "$dockerd_pid" >/dev/null 2>&1 || true
12+
fi
13+
}
14+
15+
trap cleanup EXIT INT TERM
16+
17+
if [[ "$runtime" == "isolated" ]]; then
18+
if [[ "$docker_host" != unix://* ]]; then
19+
echo "DOCKER_GIT_DOCKER_RUNTIME=isolated requires a unix:// DOCKER_HOST for the managed controller daemon." >&2
20+
exit 1
21+
fi
22+
23+
export DOCKER_HOST="$docker_host"
24+
25+
socket_path="${docker_host#unix://}"
26+
data_root="${DOCKER_GIT_DOCKER_DATA_ROOT:-/var/lib/docker}"
27+
log_path="${DOCKER_GIT_DOCKERD_LOG:-/var/log/docker-git/dockerd.log}"
28+
tcp_host="${DOCKER_GIT_DOCKERD_TCP_HOST:-tcp://0.0.0.0:2375}"
29+
default_cgroupns_mode="${DOCKER_GIT_DOCKERD_DEFAULT_CGROUPNS_MODE:-host}"
30+
31+
mkdir -p "$(dirname "$socket_path")" "$data_root" "$(dirname "$log_path")"
32+
33+
if ! docker info >/dev/null 2>&1; then
34+
# shellcheck disable=SC2086
35+
dockerd \
36+
--host="$docker_host" \
37+
--host="$tcp_host" \
38+
--tls=false \
39+
--data-root="$data_root" \
40+
--exec-opt native.cgroupdriver=cgroupfs \
41+
--default-cgroupns-mode="$default_cgroupns_mode" \
42+
${DOCKER_GIT_DOCKERD_ARGS:-} \
43+
>"$log_path" 2>&1 &
44+
dockerd_pid="$!"
45+
46+
for _ in $(seq 1 90); do
47+
if docker info >/dev/null 2>&1; then
48+
break
49+
fi
50+
if ! kill -0 "$dockerd_pid" >/dev/null 2>&1; then
51+
echo "Managed Docker daemon exited during startup." >&2
52+
tail -n 200 "$log_path" >&2 || true
53+
exit 1
54+
fi
55+
sleep 1
56+
done
57+
58+
if ! docker info >/dev/null 2>&1; then
59+
echo "Managed Docker daemon did not become ready in time." >&2
60+
tail -n 200 "$log_path" >&2 || true
61+
exit 1
62+
fi
63+
fi
64+
fi
65+
66+
bun packages/api/dist/src/main.js &
67+
api_pid="$!"
68+
wait "$api_pid"

packages/app/src/lib/core/templates-entrypoint/agent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ rm -f "$AGENT_DONE_PATH" "$AGENT_FAIL_PATH" "$AGENT_PROMPT_FILE"`,
3737
String.raw`# Collect tokens for agent environment (su - dev does not always inherit profile.d)
3838
AGENT_ENV_FILE="/run/docker-git/agent-env.sh"
3939
{
40+
[[ -f /etc/profile.d/docker-host.sh ]] && cat /etc/profile.d/docker-host.sh
4041
[[ -f /etc/profile.d/gh-token.sh ]] && cat /etc/profile.d/gh-token.sh
4142
[[ -f /etc/profile.d/claude-config.sh ]] && cat /etc/profile.d/claude-config.sh
4243
[[ -f /etc/profile.d/gemini-config.sh ]] && cat /etc/profile.d/gemini-config.sh

packages/app/src/lib/core/templates-entrypoint/base.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,25 @@ fi
9191
chown -R 1000:1000 /home/${config.sshUser}/.ssh`
9292

9393
export const renderEntrypointDockerSocket = (config: TemplateConfig): string =>
94-
`# Ensure docker socket access for ${config.sshUser}
95-
if [[ -S /var/run/docker.sock ]]; then
94+
`# Ensure Docker CLI targets only the explicitly configured daemon.
95+
if [[ -n "\${DOCKER_GIT_PROJECT_DOCKER_HOST:-}" && -z "\${DOCKER_HOST:-}" ]]; then
96+
DOCKER_HOST="$DOCKER_GIT_PROJECT_DOCKER_HOST"
97+
export DOCKER_HOST
98+
fi
99+
100+
if [[ -n "\${DOCKER_HOST:-}" ]]; then
101+
printf "export DOCKER_HOST=%q\\n" "$DOCKER_HOST" > /etc/profile.d/docker-host.sh
102+
docker_git_upsert_ssh_env "DOCKER_HOST" "$DOCKER_HOST"
103+
elif [[ -S /var/run/docker.sock ]]; then
96104
DOCKER_SOCK_GID="$(stat -c "%g" /var/run/docker.sock)"
97105
DOCKER_GROUP="$(getent group "$DOCKER_SOCK_GID" | cut -d: -f1 || true)"
98106
if [[ -z "$DOCKER_GROUP" ]]; then
99107
DOCKER_GROUP="docker"
100108
groupadd -g "$DOCKER_SOCK_GID" "$DOCKER_GROUP" || true
101109
fi
102110
usermod -aG "$DOCKER_GROUP" ${config.sshUser} || true
103-
printf "export DOCKER_HOST=unix:///var/run/docker.sock\n" > /etc/profile.d/docker-host.sh
111+
printf "export DOCKER_HOST=unix:///var/run/docker.sock\\n" > /etc/profile.d/docker-host.sh
112+
docker_git_upsert_ssh_env "DOCKER_HOST" "unix:///var/run/docker.sock"
104113
fi`
105114

106115
export const renderEntrypointZshShell = (config: TemplateConfig): string =>

packages/app/src/lib/core/templates/docker-compose.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,17 +164,20 @@ ${fragments.maybeGithubAuthSkipEnv} # Optional anonymous public GitHub clon
164164
${fragments.maybeGitTokenLabelEnv} # Optional token label selector (maps to GITHUB_TOKEN__<LABEL>/GIT_AUTH_TOKEN__<LABEL>)
165165
${fragments.maybeCodexAuthLabelEnv} # Optional Codex account label selector (maps to CODEX_AUTH_LABEL)
166166
${fragments.maybeClaudeAuthLabelEnv}${fragments.maybeAgentModeEnv}${fragments.maybeAgentAutoEnv} # Optional Claude account label selector (maps to CLAUDE_AUTH_LABEL)
167+
# Optional isolated Docker daemon endpoint injected by the API controller.
168+
DOCKER_GIT_PROJECT_DOCKER_HOST: "\${DOCKER_GIT_PROJECT_DOCKER_HOST:-}"
167169
TARGET_DIR: "${config.targetDir}"
168170
CODEX_HOME: "${config.codexHome}"
169171
${fragments.maybePlaywrightEnv}${fragments.maybeDependsOn} # bootstrap auth/env arrives through docker_git_bootstrap
170172
ports:
171-
- "127.0.0.1:${config.sshPort}:22"
173+
- "\${DOCKER_GIT_PROJECT_SSH_BIND_HOST:-127.0.0.1}:${config.sshPort}:22"
172174
${renderResourceLimits(resourceLimits)} volumes:
173175
- ${config.volumeName}:/home/${config.sshUser}
174176
- ${sharedCacheVolumeKey}:/home/${config.sshUser}/.docker-git/.cache
175177
- ${sharedCodexVolumeKey}:${config.codexHome}-shared
176178
${fragments.maybeBootstrapMounts}
177-
- /var/run/docker.sock:/var/run/docker.sock
179+
extra_hosts:
180+
- "host.docker.internal:host-gateway"
178181
dns:
179182
- 8.8.8.8
180183
- 8.8.4.4

packages/lib/src/core/templates-entrypoint/agent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ rm -f "$AGENT_DONE_PATH" "$AGENT_FAIL_PATH" "$AGENT_PROMPT_FILE"`,
3636
String.raw`# Collect tokens for agent environment (su - dev does not always inherit profile.d)
3737
AGENT_ENV_FILE="/run/docker-git/agent-env.sh"
3838
{
39+
[[ -f /etc/profile.d/docker-host.sh ]] && cat /etc/profile.d/docker-host.sh
3940
[[ -f /etc/profile.d/gh-token.sh ]] && cat /etc/profile.d/gh-token.sh
4041
[[ -f /etc/profile.d/claude-config.sh ]] && cat /etc/profile.d/claude-config.sh
4142
[[ -f /etc/profile.d/gemini-config.sh ]] && cat /etc/profile.d/gemini-config.sh

0 commit comments

Comments
 (0)