2026-03-08 17:57:46 -07:00
|
|
|
# syntax=docker/dockerfile:1.7
|
|
|
|
|
|
2026-03-06 12:18:42 -05:00
|
|
|
# Opt-in extension dependencies at build time (space-separated directory names).
|
|
|
|
|
# Example: docker build --build-arg OPENCLAW_EXTENSIONS="diagnostics-otel matrix" .
|
|
|
|
|
#
|
2026-03-07 14:26:29 -05:00
|
|
|
# Multi-stage build produces a minimal runtime image without build tools,
|
|
|
|
|
# source code, or Bun. Works with Docker, Buildx, and Podman.
|
|
|
|
|
# The ext-deps stage extracts only the package.json files we need from
|
|
|
|
|
# extensions/, so the main build layer is not invalidated by unrelated
|
|
|
|
|
# extension source changes.
|
|
|
|
|
#
|
|
|
|
|
# Two runtime variants:
|
|
|
|
|
# Default (bookworm): docker build .
|
|
|
|
|
# Slim (bookworm-slim): docker build --build-arg OPENCLAW_VARIANT=slim .
|
2026-03-06 12:18:42 -05:00
|
|
|
ARG OPENCLAW_EXTENSIONS=""
|
2026-03-07 14:26:29 -05:00
|
|
|
ARG OPENCLAW_VARIANT=default
|
2026-03-12 15:09:23 +03:00
|
|
|
ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:24-bookworm@sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"
|
|
|
|
|
ARG OPENCLAW_NODE_BOOKWORM_DIGEST="sha256:3a09aa6354567619221ef6c45a5051b671f953f0a1924d1f819ffb236e520e6b"
|
|
|
|
|
ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:24-bookworm-slim@sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"
|
|
|
|
|
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:e8e2e91b1378f83c5b2dd15f0247f34110e2fe895f6ca7719dbb780f929368eb"
|
2026-03-07 14:26:29 -05:00
|
|
|
|
|
|
|
|
# Base images are pinned to SHA256 digests for reproducible builds.
|
|
|
|
|
# Trade-off: digests must be updated manually when upstream tags move.
|
2026-03-12 15:09:23 +03:00
|
|
|
# To update, run: docker buildx imagetools inspect node:24-bookworm (or podman)
|
2026-03-08 02:55:09 +00:00
|
|
|
# and replace the digest below with the current multi-arch manifest list entry.
|
2026-03-07 14:26:29 -05:00
|
|
|
|
2026-03-08 02:55:09 +00:00
|
|
|
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS ext-deps
|
2026-03-06 12:18:42 -05:00
|
|
|
ARG OPENCLAW_EXTENSIONS
|
|
|
|
|
COPY extensions /tmp/extensions
|
2026-03-07 14:26:29 -05:00
|
|
|
# Copy package.json for opted-in extensions so pnpm resolves their deps.
|
2026-03-06 12:18:42 -05:00
|
|
|
RUN mkdir -p /out && \
|
|
|
|
|
for ext in $OPENCLAW_EXTENSIONS; do \
|
|
|
|
|
if [ -f "/tmp/extensions/$ext/package.json" ]; then \
|
|
|
|
|
mkdir -p "/out/$ext" && \
|
|
|
|
|
cp "/tmp/extensions/$ext/package.json" "/out/$ext/package.json"; \
|
|
|
|
|
fi; \
|
|
|
|
|
done
|
|
|
|
|
|
2026-03-07 14:26:29 -05:00
|
|
|
# ── Stage 2: Build ──────────────────────────────────────────────
|
2026-03-08 02:55:09 +00:00
|
|
|
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS build
|
2026-03-07 14:26:29 -05:00
|
|
|
|
2026-03-12 16:45:12 +00:00
|
|
|
# Install Bun (required for build scripts). Retry the whole bootstrap flow to
|
|
|
|
|
# tolerate transient 5xx failures from bun.sh/GitHub during CI image builds.
|
|
|
|
|
RUN set -eux; \
|
|
|
|
|
for attempt in 1 2 3 4 5; do \
|
|
|
|
|
if curl --retry 5 --retry-all-errors --retry-delay 2 -fsSL https://bun.sh/install | bash; then \
|
|
|
|
|
break; \
|
|
|
|
|
fi; \
|
|
|
|
|
if [ "$attempt" -eq 5 ]; then \
|
|
|
|
|
exit 1; \
|
|
|
|
|
fi; \
|
|
|
|
|
sleep $((attempt * 2)); \
|
|
|
|
|
done
|
2026-03-07 14:26:29 -05:00
|
|
|
ENV PATH="/root/.bun/bin:${PATH}"
|
|
|
|
|
|
|
|
|
|
RUN corepack enable
|
|
|
|
|
|
|
|
|
|
WORKDIR /app
|
|
|
|
|
|
|
|
|
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
|
|
|
|
COPY ui/package.json ./ui/package.json
|
|
|
|
|
COPY patches ./patches
|
|
|
|
|
|
|
|
|
|
COPY --from=ext-deps /out/ ./extensions/
|
|
|
|
|
|
|
|
|
|
# Reduce OOM risk on low-memory hosts during dependency installation.
|
|
|
|
|
# Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
|
2026-03-08 17:57:46 -07:00
|
|
|
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
|
|
|
|
|
NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
|
2026-03-07 14:26:29 -05:00
|
|
|
|
|
|
|
|
COPY . .
|
|
|
|
|
|
2026-03-08 16:07:04 -07:00
|
|
|
# Normalize extension paths now so runtime COPY preserves safe modes
|
|
|
|
|
# without adding a second full extensions layer.
|
|
|
|
|
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
|
|
|
|
|
|
2026-03-07 14:26:29 -05:00
|
|
|
# A2UI bundle may fail under QEMU cross-compilation (e.g. building amd64
|
|
|
|
|
# on Apple Silicon). CI builds natively per-arch so this is a no-op there.
|
|
|
|
|
# Stub it so local cross-arch builds still succeed.
|
|
|
|
|
RUN pnpm canvas:a2ui:bundle || \
|
|
|
|
|
(echo "A2UI bundle: creating stub (non-fatal)" && \
|
|
|
|
|
mkdir -p src/canvas-host/a2ui && \
|
|
|
|
|
echo "/* A2UI bundle unavailable in this build */" > src/canvas-host/a2ui/a2ui.bundle.js && \
|
|
|
|
|
echo "stub" > src/canvas-host/a2ui/.bundle.hash && \
|
|
|
|
|
rm -rf vendor/a2ui apps/shared/OpenClawKit/Tools/CanvasA2UI)
|
2026-03-08 16:07:04 -07:00
|
|
|
RUN pnpm build:docker
|
2026-03-07 14:26:29 -05:00
|
|
|
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
|
|
|
|
|
ENV OPENCLAW_PREFER_PNPM=1
|
|
|
|
|
RUN pnpm ui:build
|
|
|
|
|
|
2026-03-08 16:07:04 -07:00
|
|
|
# Prune dev dependencies and strip build-only metadata before copying
|
|
|
|
|
# runtime assets into the final image.
|
|
|
|
|
FROM build AS runtime-assets
|
|
|
|
|
RUN CI=true pnpm prune --prod && \
|
|
|
|
|
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete
|
|
|
|
|
|
2026-03-07 14:26:29 -05:00
|
|
|
# ── Runtime base images ─────────────────────────────────────────
|
2026-03-08 02:55:09 +00:00
|
|
|
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS base-default
|
|
|
|
|
ARG OPENCLAW_NODE_BOOKWORM_DIGEST
|
2026-03-12 15:09:23 +03:00
|
|
|
LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm" \
|
2026-03-08 02:55:09 +00:00
|
|
|
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_DIGEST}"
|
2026-03-07 14:26:29 -05:00
|
|
|
|
2026-03-08 02:55:09 +00:00
|
|
|
FROM ${OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE} AS base-slim
|
|
|
|
|
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST
|
2026-03-12 15:09:23 +03:00
|
|
|
LABEL org.opencontainers.image.base.name="docker.io/library/node:24-bookworm-slim" \
|
2026-03-08 02:55:09 +00:00
|
|
|
org.opencontainers.image.base.digest="${OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST}"
|
2026-03-07 14:26:29 -05:00
|
|
|
|
|
|
|
|
# ── Stage 3: Runtime ────────────────────────────────────────────
|
|
|
|
|
FROM base-${OPENCLAW_VARIANT}
|
|
|
|
|
ARG OPENCLAW_VARIANT
|
2026-01-02 13:52:08 +02:00
|
|
|
|
2026-03-01 19:22:44 -08:00
|
|
|
# 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
|
2026-03-07 14:26:29 -05:00
|
|
|
LABEL org.opencontainers.image.source="https://github.com/openclaw/openclaw" \
|
2026-03-01 19:22:44 -08:00
|
|
|
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"
|
|
|
|
|
|
2026-03-07 14:26:29 -05:00
|
|
|
WORKDIR /app
|
2026-01-06 15:05:19 +01:00
|
|
|
|
2026-03-07 14:26:29 -05:00
|
|
|
# Install system utilities present in bookworm but missing in bookworm-slim.
|
|
|
|
|
# On the full bookworm image these are already installed (apt-get is a no-op).
|
2026-03-08 17:57:46 -07:00
|
|
|
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
|
|
|
|
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
|
|
|
|
apt-get update && \
|
2026-03-07 14:26:29 -05:00
|
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
2026-03-08 17:57:46 -07:00
|
|
|
procps hostname curl git openssl
|
2026-01-02 13:52:08 +02:00
|
|
|
|
2026-02-20 21:46:30 -06:00
|
|
|
RUN chown node:node /app
|
2026-01-02 13:52:08 +02:00
|
|
|
|
2026-03-08 16:07:04 -07:00
|
|
|
COPY --from=runtime-assets --chown=node:node /app/dist ./dist
|
|
|
|
|
COPY --from=runtime-assets --chown=node:node /app/node_modules ./node_modules
|
|
|
|
|
COPY --from=runtime-assets --chown=node:node /app/package.json .
|
|
|
|
|
COPY --from=runtime-assets --chown=node:node /app/openclaw.mjs .
|
|
|
|
|
COPY --from=runtime-assets --chown=node:node /app/extensions ./extensions
|
|
|
|
|
COPY --from=runtime-assets --chown=node:node /app/skills ./skills
|
|
|
|
|
COPY --from=runtime-assets --chown=node:node /app/docs ./docs
|
|
|
|
|
|
|
|
|
|
# Keep pnpm available in the runtime image for container-local workflows.
|
|
|
|
|
# Use a shared Corepack home so the non-root `node` user does not need a
|
|
|
|
|
# first-run network fetch when invoking pnpm.
|
|
|
|
|
ENV COREPACK_HOME=/usr/local/share/corepack
|
|
|
|
|
RUN install -d -m 0755 "$COREPACK_HOME" && \
|
|
|
|
|
corepack enable && \
|
2026-03-12 16:45:12 +00:00
|
|
|
for attempt in 1 2 3 4 5; do \
|
|
|
|
|
if corepack prepare "$(node -p "require('./package.json').packageManager")" --activate; then \
|
|
|
|
|
break; \
|
|
|
|
|
fi; \
|
|
|
|
|
if [ "$attempt" -eq 5 ]; then \
|
|
|
|
|
exit 1; \
|
|
|
|
|
fi; \
|
|
|
|
|
sleep $((attempt * 2)); \
|
|
|
|
|
done && \
|
2026-03-08 16:07:04 -07:00
|
|
|
chmod -R a+rX "$COREPACK_HOME"
|
2026-03-08 04:05:53 +00:00
|
|
|
|
2026-03-07 14:26:29 -05:00
|
|
|
# Install additional system packages needed by your skills or extensions.
|
|
|
|
|
# Example: docker build --build-arg OPENCLAW_DOCKER_APT_PACKAGES="python3 wget" .
|
2026-01-30 03:15:10 +01:00
|
|
|
ARG OPENCLAW_DOCKER_APT_PACKAGES=""
|
2026-03-08 17:57:46 -07:00
|
|
|
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
|
|
|
|
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
|
|
|
|
if [ -n "$OPENCLAW_DOCKER_APT_PACKAGES" ]; then \
|
2026-01-11 00:06:19 +00:00
|
|
|
apt-get update && \
|
2026-03-08 17:57:46 -07:00
|
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends $OPENCLAW_DOCKER_APT_PACKAGES; \
|
2026-01-11 00:06:19 +00:00
|
|
|
fi
|
|
|
|
|
|
2026-02-16 10:19:32 -08:00
|
|
|
# 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.
|
2026-03-07 14:26:29 -05:00
|
|
|
# Must run after node_modules COPY so playwright-core is available.
|
2026-02-16 10:19:32 -08:00
|
|
|
ARG OPENCLAW_INSTALL_BROWSER=""
|
2026-03-08 17:57:46 -07:00
|
|
|
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
|
|
|
|
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
|
|
|
|
if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
|
2026-02-16 10:19:32 -08:00
|
|
|
apt-get update && \
|
|
|
|
|
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends xvfb && \
|
2026-02-21 15:48:27 +05:30
|
|
|
mkdir -p /home/node/.cache/ms-playwright && \
|
|
|
|
|
PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright \
|
2026-02-16 10:46:38 -08:00
|
|
|
node /app/node_modules/playwright-core/cli.js install --with-deps chromium && \
|
2026-03-08 17:57:46 -07:00
|
|
|
chown -R node:node /home/node/.cache/ms-playwright; \
|
2026-02-16 10:19:32 -08:00
|
|
|
fi
|
|
|
|
|
|
feat(docker): add opt-in sandbox support for Docker deployments (#29974)
* 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>
2026-03-02 08:06:10 +01:00
|
|
|
# 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"
|
2026-03-08 17:57:46 -07:00
|
|
|
RUN --mount=type=cache,id=openclaw-bookworm-apt-cache,target=/var/cache/apt,sharing=locked \
|
|
|
|
|
--mount=type=cache,id=openclaw-bookworm-apt-lists,target=/var/lib/apt,sharing=locked \
|
|
|
|
|
if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
|
feat(docker): add opt-in sandbox support for Docker deployments (#29974)
* 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>
2026-03-02 08:06:10 +01:00
|
|
|
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:]')" && \
|
2026-03-03 09:46:06 +08:00
|
|
|
actual_fingerprint="$(gpg --batch --show-keys --with-colons /tmp/docker.gpg.asc | awk -F: '$1 == "fpr" { print toupper($10); exit }')" && \
|
feat(docker): add opt-in sandbox support for Docker deployments (#29974)
* 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>
2026-03-02 08:06:10 +01:00
|
|
|
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 \
|
2026-03-08 17:57:46 -07:00
|
|
|
docker-ce-cli docker-compose-plugin; \
|
feat(docker): add opt-in sandbox support for Docker deployments (#29974)
* 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>
2026-03-02 08:06:10 +01:00
|
|
|
fi
|
|
|
|
|
|
2026-02-26 23:57:28 -05:00
|
|
|
# Expose the CLI binary without requiring npm global writes as non-root.
|
|
|
|
|
RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw \
|
|
|
|
|
&& chmod 755 /app/openclaw.mjs
|
2026-01-02 13:52:08 +02:00
|
|
|
|
|
|
|
|
ENV NODE_ENV=production
|
|
|
|
|
|
2026-01-25 20:41:20 -03:00
|
|
|
# Security hardening: Run as non-root user
|
2026-03-12 15:09:23 +03:00
|
|
|
# The node:24-bookworm image includes a 'node' user (uid 1000)
|
2026-01-25 20:41:20 -03:00
|
|
|
# This reduces the attack surface by preventing container escape via root privileges
|
|
|
|
|
USER node
|
|
|
|
|
|
2026-02-02 03:46:30 +05:30
|
|
|
# Start gateway server with default config.
|
|
|
|
|
# Binds to loopback (127.0.0.1) by default for security.
|
|
|
|
|
#
|
2026-03-02 09:15:27 +05:30
|
|
|
# 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
|
|
|
|
|
#
|
2026-03-01 20:36:58 -08:00
|
|
|
# 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.
|
2026-03-02 07:52:14 +03:00
|
|
|
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))"
|
2026-02-06 17:18:10 -08:00
|
|
|
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
|