feat(sandbox): add Docker-in-Docker path remapping for browser sandboxes
When the gateway runs inside a Docker container and creates sandbox containers, volume mount paths need to be host paths, not container paths. Changes: - Add remapPathForDinD() function to remap container paths to host paths - Add CLAWDBOT_SANDBOX_HOST_CONFIG_DIR and CLAWDBOT_SANDBOX_HOST_WORKSPACE_DIR environment variables to docker-compose.yml - Use path remapping in both sandbox container and browser sandbox creation - Add Docker CLI to gateway Dockerfile for Docker-in-Docker support The path remapping is a no-op when the environment variables are not set, so bare metal installations are unaffected. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fc0fca5108
commit
51decc6535
12
Dockerfile
12
Dockerfile
@ -4,6 +4,18 @@ FROM node:22-bookworm
|
||||
RUN curl -fsSL https://bun.sh/install | bash
|
||||
ENV PATH="/root/.bun/bin:${PATH}"
|
||||
|
||||
# Install Docker CLI from official Docker repo (Debian docker.io is too old for Docker Desktop)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends ca-certificates curl && \
|
||||
install -m 0755 -d /etc/apt/keyrings && \
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc && \
|
||||
chmod a+r /etc/apt/keyrings/docker.asc && \
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends docker-ce-cli && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@ -9,6 +9,9 @@ services:
|
||||
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
||||
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
|
||||
# Docker-in-Docker: host paths for sandbox container volume mounts
|
||||
CLAWDBOT_SANDBOX_HOST_CONFIG_DIR: ${CLAWDBOT_CONFIG_DIR}
|
||||
CLAWDBOT_SANDBOX_HOST_WORKSPACE_DIR: ${CLAWDBOT_WORKSPACE_DIR}
|
||||
volumes:
|
||||
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
|
||||
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
dockerContainerState,
|
||||
execDocker,
|
||||
readDockerPort,
|
||||
remapPathForDinD,
|
||||
} from "./docker.js";
|
||||
import { updateBrowserRegistry } from "./registry.js";
|
||||
import { slugifySessionKey } from "./shared.js";
|
||||
@ -134,12 +135,15 @@ export async function ensureSandboxBrowser(params: {
|
||||
params.cfg.workspaceAccess === "ro" && params.workspaceDir === params.agentWorkspaceDir
|
||||
? ":ro"
|
||||
: "";
|
||||
args.push("-v", `${params.workspaceDir}:${params.cfg.docker.workdir}${mainMountSuffix}`);
|
||||
// Remap paths for Docker-in-Docker scenarios
|
||||
const hostWorkspaceDir = remapPathForDinD(params.workspaceDir);
|
||||
args.push("-v", `${hostWorkspaceDir}:${params.cfg.docker.workdir}${mainMountSuffix}`);
|
||||
if (params.cfg.workspaceAccess !== "none" && params.workspaceDir !== params.agentWorkspaceDir) {
|
||||
const agentMountSuffix = params.cfg.workspaceAccess === "ro" ? ":ro" : "";
|
||||
const hostAgentWorkspaceDir = remapPathForDinD(params.agentWorkspaceDir);
|
||||
args.push(
|
||||
"-v",
|
||||
`${params.agentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`,
|
||||
`${hostAgentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`,
|
||||
);
|
||||
}
|
||||
args.push("-p", `127.0.0.1::${params.cfg.browser.cdpPort}`);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import os from "node:os";
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
@ -8,6 +9,41 @@ import { computeSandboxConfigHash } from "./config-hash.js";
|
||||
import { resolveSandboxAgentId, resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
|
||||
import type { SandboxConfig, SandboxDockerConfig, SandboxWorkspaceAccess } from "./types.js";
|
||||
|
||||
/**
|
||||
* For Docker-in-Docker scenarios, remap container paths to host paths.
|
||||
* When the gateway runs in a container and creates sandbox containers,
|
||||
* the volume mount paths must be host paths, not container paths.
|
||||
*
|
||||
* Uses environment variables set by docker-compose:
|
||||
* - CLAWDBOT_SANDBOX_HOST_CONFIG_DIR: host path for ~/.clawdbot
|
||||
* - CLAWDBOT_SANDBOX_HOST_WORKSPACE_DIR: host path for ~/clawd
|
||||
*/
|
||||
export function remapPathForDinD(containerPath: string): string {
|
||||
const hostConfigDir = process.env.CLAWDBOT_SANDBOX_HOST_CONFIG_DIR;
|
||||
const hostWorkspaceDir = process.env.CLAWDBOT_SANDBOX_HOST_WORKSPACE_DIR;
|
||||
|
||||
// If no host path mappings are set, we're not in Docker-in-Docker mode
|
||||
if (!hostConfigDir && !hostWorkspaceDir) {
|
||||
return containerPath;
|
||||
}
|
||||
|
||||
const home = os.homedir();
|
||||
const containerConfigDir = `${home}/.clawdbot`;
|
||||
const containerWorkspaceDir = `${home}/clawd`;
|
||||
|
||||
// Remap config directory paths
|
||||
if (hostConfigDir && containerPath.startsWith(containerConfigDir)) {
|
||||
return containerPath.replace(containerConfigDir, hostConfigDir);
|
||||
}
|
||||
|
||||
// Remap workspace directory paths
|
||||
if (hostWorkspaceDir && containerPath.startsWith(containerWorkspaceDir)) {
|
||||
return containerPath.replace(containerWorkspaceDir, hostWorkspaceDir);
|
||||
}
|
||||
|
||||
return containerPath;
|
||||
}
|
||||
|
||||
const HOT_CONTAINER_WINDOW_MS = 5 * 60 * 1000;
|
||||
|
||||
export function execDocker(args: string[], opts?: { allowFailure?: boolean }) {
|
||||
@ -189,12 +225,15 @@ async function createSandboxContainer(params: {
|
||||
args.push("--workdir", cfg.workdir);
|
||||
const mainMountSuffix =
|
||||
params.workspaceAccess === "ro" && workspaceDir === params.agentWorkspaceDir ? ":ro" : "";
|
||||
args.push("-v", `${workspaceDir}:${cfg.workdir}${mainMountSuffix}`);
|
||||
// Remap paths for Docker-in-Docker scenarios
|
||||
const hostWorkspaceDir = remapPathForDinD(workspaceDir);
|
||||
args.push("-v", `${hostWorkspaceDir}:${cfg.workdir}${mainMountSuffix}`);
|
||||
if (params.workspaceAccess !== "none" && workspaceDir !== params.agentWorkspaceDir) {
|
||||
const agentMountSuffix = params.workspaceAccess === "ro" ? ":ro" : "";
|
||||
const hostAgentWorkspaceDir = remapPathForDinD(params.agentWorkspaceDir);
|
||||
args.push(
|
||||
"-v",
|
||||
`${params.agentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`,
|
||||
`${hostAgentWorkspaceDir}:${SANDBOX_AGENT_WORKSPACE_MOUNT}${agentMountSuffix}`,
|
||||
);
|
||||
}
|
||||
args.push(cfg.image, "sleep", "infinity");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user