mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
* feat(docker): add opt-in sandbox support for Docker deployments Enable Docker-based sandbox isolation via OPENCLAW_SANDBOX=1 env var in docker-setup.sh. This is a prerequisite for agents.defaults.sandbox to function in any Docker deployment (self-hosted, Hostinger, DigitalOcean). Changes: - Dockerfile: add OPENCLAW_INSTALL_DOCKER_CLI build arg (~50MB, opt-in) - docker-compose.yml: add commented-out docker.sock mount with docs - docker-setup.sh: auto-detect Docker socket, inject mount, detect GID, build sandbox image, configure sandbox defaults, add group_add All changes are opt-in. Zero impact on existing deployments. Usage: OPENCLAW_SANDBOX=1 ./docker-setup.sh Closes #29933 Related: #7575, #7827, #28401, #10361, #12505, #28326 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: address code review feedback on sandbox support - Persist OPENCLAW_SANDBOX, DOCKER_GID, OPENCLAW_INSTALL_DOCKER_CLI to .env via upsert_env so group_add survives re-runs - Show config set errors instead of swallowing them silently; report partial failure when sandbox config is incomplete - Warn when Dockerfile.sandbox is missing but sandbox config is still applied (sandbox image won't exist) - Fix non-canonical whitespace in apt sources.list entry by using printf instead of echo with line continuation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove `local` outside function and guard sandbox behind Docker CLI check - Remove `local` keyword from top-level `sandbox_config_ok` assignment which caused script exit under `set -euo pipefail` (bash `local` outside a function is an error) - Add Docker CLI prerequisite check for pre-built (non-local) images: runs `docker --version` inside the container and skips sandbox setup with a clear warning if the CLI is missing - Split sandbox block so config is only applied after prerequisites pass Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: defer docker.sock mount until sandbox prerequisites pass Move Docker socket mounting from the early setup phase (before image build/pull) to a dedicated compose overlay created only after: 1. Docker CLI is verified inside the container image 2. /var/run/docker.sock exists on the host Previously the socket was mounted optimistically at startup, leaving the host Docker daemon exposed even when sandbox setup was later skipped due to missing Docker CLI. Now the gateway starts without the socket, and a docker-compose.sandbox.yml overlay is generated only when all prerequisites pass. The gateway restart at the end of sandbox setup picks up both the socket mount and sandbox config. Also moves group_add from write_extra_compose() into the sandbox overlay, keeping all sandbox-specific compose configuration together. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(docker): fix sandbox docs URL in setup output * Docker: harden sandbox setup fallback behavior * Tests: cover docker-setup sandbox edge paths * Docker: roll back sandbox mode on partial config failure * Tests: assert sandbox mode rollback on partial setup * Docs: document Docker sandbox bootstrap env controls * Changelog: credit Docker sandbox bootstrap hardening * Update CHANGELOG.md * Docker: verify Docker apt signing key fingerprint * Docker: avoid sandbox overlay deps during policy writes * Tests: assert no-deps sandbox rollback gateway recreate * Docs: mention OPENCLAW_INSTALL_DOCKER_CLI in Docker env vars --------- Co-authored-by: Jakub Karwowski <jakubkarwowski@Mac.lan> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
134 lines
6.4 KiB
Docker
134 lines
6.4 KiB
Docker
FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
|
|
|
|
# OCI base-image metadata for downstream image consumers.
|
|
# If you change these annotations, also update:
|
|
# - docs/install/docker.md ("Base image metadata" section)
|
|
# - https://docs.openclaw.ai/install/docker
|
|
LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm" \
|
|
org.opencontainers.image.base.digest="sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935" \
|
|
org.opencontainers.image.source="https://github.com/openclaw/openclaw" \
|
|
org.opencontainers.image.url="https://openclaw.ai" \
|
|
org.opencontainers.image.documentation="https://docs.openclaw.ai/install/docker" \
|
|
org.opencontainers.image.licenses="MIT" \
|
|
org.opencontainers.image.title="OpenClaw" \
|
|
org.opencontainers.image.description="OpenClaw gateway and CLI runtime container image"
|
|
|
|
# Install Bun (required for build scripts)
|
|
RUN curl -fsSL https://bun.sh/install | bash
|
|
ENV PATH="/root/.bun/bin:${PATH}"
|
|
|
|
RUN corepack enable
|
|
|
|
WORKDIR /app
|
|
RUN chown node:node /app
|
|
|
|
ARG OPENCLAW_DOCKER_APT_PACKAGES=""
|
|
RUN if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
|
|
apt-get update && \
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES && \
|
|
apt-get clean && \
|
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
|
fi
|
|
|
|
COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
|
COPY --chown=node:node ui/package.json ./ui/package.json
|
|
COPY --chown=node:node patches ./patches
|
|
COPY --chown=node:node scripts ./scripts
|
|
|
|
USER node
|
|
# Reduce OOM risk on low-memory hosts during dependency installation.
|
|
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
|
RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
|
|
|
# Optionally install Chromium and Xvfb for browser automation.
|
|
# Build with: docker build --build-arg OPENCLAW_INSTALL_BROWSER=1 ...
|
|
# Adds ~300MB but eliminates the 60-90s Playwright install on every container start.
|
|
# Must run after pnpm install so playwright-core is available in node_modules.
|
|
USER root
|
|
ARG OPENCLAW_INSTALL_BROWSER=""
|
|
RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
|
|
apt-get update && \
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends xvfb && \
|
|
mkdir -p /home/node/.cache/ms-playwright && \
|
|
PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright \
|
|
node /app/node_modules/playwright-core/cli.js install --with-deps chromium && \
|
|
chown -R node:node /home/node/.cache/ms-playwright && \
|
|
apt-get clean && \
|
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
|
fi
|
|
|
|
# Optionally install Docker CLI for sandbox container management.
|
|
# Build with: docker build --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1 ...
|
|
# Adds ~50MB. Only the CLI is installed — no Docker daemon.
|
|
# Required for agents.defaults.sandbox to function in Docker deployments.
|
|
ARG OPENCLAW_INSTALL_DOCKER_CLI=""
|
|
ARG OPENCLAW_DOCKER_GPG_FINGERPRINT="9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
|
|
RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
|
|
apt-get update && \
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
ca-certificates curl gnupg && \
|
|
install -m 0755 -d /etc/apt/keyrings && \
|
|
# Verify Docker apt signing key fingerprint before trusting it as a root key.
|
|
# Update OPENCLAW_DOCKER_GPG_FINGERPRINT when Docker rotates release keys.
|
|
curl -fsSL https://download.docker.com/linux/debian/gpg -o /tmp/docker.gpg.asc && \
|
|
expected_fingerprint="$(printf '%s' "$OPENCLAW_DOCKER_GPG_FINGERPRINT" | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')" && \
|
|
actual_fingerprint="$(gpg --batch --show-keys --with-colons /tmp/docker.gpg.asc | awk -F: '$1 == \"fpr\" { print toupper($10); exit }')" && \
|
|
if [ -z "$actual_fingerprint" ] || [ "$actual_fingerprint" != "$expected_fingerprint" ]; then \
|
|
echo "ERROR: Docker apt key fingerprint mismatch (expected $expected_fingerprint, got ${actual_fingerprint:-<empty>})" >&2; \
|
|
exit 1; \
|
|
fi && \
|
|
gpg --dearmor -o /etc/apt/keyrings/docker.gpg /tmp/docker.gpg.asc && \
|
|
rm -f /tmp/docker.gpg.asc && \
|
|
chmod a+r /etc/apt/keyrings/docker.gpg && \
|
|
printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable\n' \
|
|
"$(dpkg --print-architecture)" > /etc/apt/sources.list.d/docker.list && \
|
|
apt-get update && \
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
docker-ce-cli docker-compose-plugin && \
|
|
apt-get clean && \
|
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
|
|
fi
|
|
|
|
USER node
|
|
COPY --chown=node:node . .
|
|
# Normalize copied plugin/agent paths so plugin safety checks do not reject
|
|
# world-writable directories inherited from source file modes.
|
|
RUN for dir in /app/extensions /app/.agent /app/.agents; do \
|
|
if [ -d "$dir" ]; then \
|
|
find "$dir" -type d -exec chmod 755 {} +; \
|
|
find "$dir" -type f -exec chmod 644 {} +; \
|
|
fi; \
|
|
done
|
|
RUN pnpm build
|
|
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
|
ENV OPENCLAW_PREFER_PNPM=1
|
|
RUN pnpm ui:build
|
|
|
|
# Expose the CLI binary without requiring npm global writes as non-root.
|
|
USER root
|
|
RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
|
|
&& chmod 755 /app/openclaw.mjs
|
|
|
|
ENV NODE_ENV=production
|
|
|
|
# Security hardening: Run as non-root user
|
|
# The node:22-bookworm image includes a 'node' user (uid 1000)
|
|
# This reduces the attack surface by preventing container escape via root privileges
|
|
USER node
|
|
|
|
# Start gateway server with default config.
|
|
# Binds to loopback (127.0.0.1) by default for security.
|
|
#
|
|
# IMPORTANT: With Docker bridge networking (-p 18789:18789), loopback bind
|
|
# makes the gateway unreachable from the host. Either:
|
|
# - Use --network host, OR
|
|
# - Override --bind to "lan" (0.0.0.0) and set auth credentials
|
|
#
|
|
# Built-in probe endpoints for container health checks:
|
|
# - GET /healthz (liveness) and GET /readyz (readiness)
|
|
# - aliases: /health and /ready
|
|
# For external access from host/ingress, override bind to "lan" and set auth.
|
|
HEALTHCHECK --interval=3m --timeout=10s --start-period=15s --retries=3 \
|
|
CMD node -e "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
|
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
|