1- FROM node:22-bookworm
1+ FROM node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
2+ # OCI base-image metadata for downstream image consumers.
3+ #
4+ # If you change these annotations, also update:
5+ # - docs/install/docker.md ("Base image metadata" section)
6+ # - https://docs.openclaw.ai/install/docker
7+ LABEL org.opencontainers.image.base.name="docker.io/library/node:22-bookworm" \
8+ org.opencontainers.image.base.digest="sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935" \
9+ org.opencontainers.image.source="https://github.com/openclaw/openclaw" \
10+ org.opencontainers.image.url="https://openclaw.ai" \
11+ org.opencontainers.image.documentation="https://docs.openclaw.ai/install/docker" \
12+ org.opencontainers.image.licenses="MIT" \
13+ org.opencontainers.image.title="OpenClaw" \
14+ org.opencontainers.image.description="OpenClaw gateway and CLI runtime container image"
215
316# Install Bun (required for build scripts)
417RUN curl -fsSL https://bun.sh/install | bash
@@ -23,7 +36,9 @@ COPY --chown=node:node patches ./patches
2336COPY --chown=node:node scripts ./scripts
2437
2538USER node
26- RUN pnpm install --frozen-lockfile
39+ # Reduce OOM risk on low-memory hosts during dependency installation.
40+ # Docker builds on small VMs may otherwise fail with "Killed" (exit 137).
41+ RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
2742
2843# Optionally install Chromium and Xvfb for browser automation.
2944# Build with: docker build --build-arg OPENCLAW_INSTALL_BROWSER=1 ...
@@ -42,30 +57,74 @@ RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \
4257 rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
4358 fi
4459
60+ # Optionally install Docker CLI for sandbox container management.
61+ # Build with: docker build --build-arg OPENCLAW_INSTALL_DOCKER_CLI=1 ...
62+ # Adds ~50MB. Only the CLI is installed - no Docker daemon.
63+ # Required for agents.defaults.sandbox to function in Docker deployments.
64+ ARG OPENCLAW_INSTALL_DOCKER_CLI=""
65+ ARG OPENCLAW_DOCKER_GPG_FINGERPRINT="9DC858229FC7DD38854AE2D88D81803C0EBFCD88"
66+ RUN if [ -n "$OPENCLAW_INSTALL_DOCKER_CLI" ]; then \
67+ apt-get update && \
68+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
69+ ca-certificates curl gnupg && \
70+ install -m 0755 -d /etc/apt/keyrings && \
71+ # Verify Docker apt signing key fingerprint before trusting it as a root key.
72+ # Update OPENCLAW_DOCKER_GPG_FINGERPRINT when Docker rotates release keys.
73+ curl -fsSL https://download.docker.com/linux/debian/gpg -o /tmp/docker.gpg.asc && \
74+ expected_fingerprint="$(printf '%s' " $OPENCLAW_DOCKER_GPG_FINGERPRINT" | tr '[:lower:]' '[:upper:]' | tr -d '[:space:]')" && \
75+ actual_fingerprint="$(gpg --batch --show-keys --with-colons /tmp/docker.gpg.asc | awk -F: '$1 == " fpr" { print toupper($10); exit }')" && \
76+ if [ -z "$actual_fingerprint" ] || [ "$actual_fingerprint" != "$expected_fingerprint" ]; then \
77+ echo "ERROR: Docker apt key fingerprint mismatch (expected $expected_fingerprint, got ${actual_fingerprint:-<empty>})" >&2; \
78+ exit 1; \
79+ fi && \
80+ gpg --dearmor -o /etc/apt/keyrings/docker.gpg /tmp/docker.gpg.asc && \
81+ rm -f /tmp/docker.gpg.asc && \
82+ chmod a+r /etc/apt/keyrings/docker.gpg && \
83+ printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable\n ' \
84+ "$(dpkg --print-architecture)" > /etc/apt/sources.list.d/docker.list && \
85+ apt-get update && \
86+ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
87+ docker-ce-cli docker-compose-plugin && \
88+ apt-get clean && \
89+ rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*; \
90+ fi
91+
4592USER node
4693COPY --chown=node:node . .
94+ # Normalize copied plugin/agent paths so plugin safety checks do not reject
95+ # world-writable directories inherited from source file modes.
96+ RUN for dir in /app/extensions /app/.agent /app/.agents; do \
97+ if [ -d "$dir" ]; then \
98+ find "$dir" -type d -exec chmod 755 {} +; \
99+ find "$dir" -type f -exec chmod 644 {} +; \
100+ fi; \
101+ done
102+
47103RUN pnpm build
104+
48105# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
49106ENV OPENCLAW_PREFER_PNPM=1
50107RUN pnpm ui:build
51108
52- ARG OPENCLAW_VERSION=""
53- ENV OPENCLAW_VERSION=$OPENCLAW_VERSION
54- ENV OPENCLAW_SERVICE_VERSION=$OPENCLAW_VERSION
55-
56- ENV NODE_ENV=production
57-
58109# ---- add openclaw command ----
59- # Makes `openclaw ...` == `node /app/dist/index.js ...`
110+ # Makes `openclaw ...` follow the upstream launcher while preserving
111+ # the customized CMD shape used by this image.
60112USER root
61113RUN printf '%s\n ' \
62114 '#!/bin/sh' \
63115 'set -e' \
64- 'exec node /app/dist/index.js "$@"' \
116+ 'exec node /app/openclaw.mjs "$@"' \
65117 > /usr/local/bin/openclaw \
66118 && chmod +x /usr/local/bin/openclaw
67119# -----------------------------
68120
121+ USER node
122+ # Preinstall bundled plugin into the image so containers start with it present.
123+ # OpenClaw stores npm-installed plugins under ~/.openclaw/extensions by default.
124+ RUN openclaw plugins install @sliverp/qqbot@latest
125+
126+ ENV NODE_ENV=production
127+
69128# Security hardening: Run as non-root user
70129# The node:22-bookworm image includes a 'node' user (uid 1000)
71130# This reduces the attack surface by preventing container escape via root privileges
@@ -74,7 +133,16 @@ USER node
74133# Start gateway server with default config.
75134# Binds to loopback (127.0.0.1) by default for security.
76135#
77- # For container platforms requiring external health checks:
78- # 1. Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD env var
79- # 2. Override CMD: ["node","openclaw.mjs","gateway","--allow-unconfigured","--bind","lan"]
136+ # IMPORTANT: With Docker bridge networking (-p 18789:18789), loopback bind
137+ # makes the gateway unreachable from the host. Either:
138+ # - Use --network host, OR
139+ # - Override --bind to "lan" (0.0.0.0) and set auth credentials
140+ #
141+ # Built-in probe endpoints for container health checks:
142+ # - GET /healthz (liveness) and GET /readyz (readiness)
143+ # - aliases: /health and /ready
144+ # For external access from host/ingress, override bind to "lan" and set auth.
145+ HEALTHCHECK --interval=3m --timeout=10s --start-period=15s --retries=3 \
146+ CMD node -e "fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
147+
80148CMD ["openclaw" , "gateway" , "--allow-unconfigured" ]
0 commit comments