Skip to content

Docker と Fly デプロイ

源码版本v2026.6.11

責務

Dockerfile(Dockerfile CMD:343-344) は OpenClaw のコンテナ化 (containerization) デプロイのルート入口です:多段ビルドで最小 runtime イメージを構築し,デフォルトで node openclaw.mjs gateway を起動します。fly.toml(fly.toml) は Fly.io のデプロイ記述で,Docker イメージを Fly の vm + 永続ボリュームに掛けます。render.yaml(render.yaml) は Render.com の同等設定です。docker-compose.yml(docker-compose.yml) は gateway + CLI の 2 サービスを編成し,ローカル開発でもセルフホストでも直接使えます。

このデプロイ形態は Daemon:システムサービス と補完します:コンテナには launchd/systemd/schtasks がなく,コンテナオーケストレータ(Docker Engine / Fly machine / Render)が「システムサービスマネージャ」となり,ブート自動起動と崩壊再起動を担います。OpenClaw 自身は健全性チェック (health check) エンドポイントを露出 + 標準信号 (SIGTERM) で綺麗に終了するだけで,残りはオーケストレータに任せます。

設計動機

なぜコンテナ化デプロイを別層に抽出するのか?

  1. イメージを最小に:node:24-bookworm の完全イメージは 1GB+ ですが,実行時には node:24-bookworm-slim + ca-certificates/curl/git/tini などの少量システムパッケージだけで済みます。多段ビルドでビルド層(Bun/pnpm install + pnpm build:docker + pnpm ui:build)と実行層を分け,最終イメージには Bun、ソース、dev dependencies、.d.ts/.map を入れません(prune d.ts and maps:149)。
  2. セキュリティデフォルト:USER node(USER node:327)で非 root 実行;cap_drop: [NET_RAW, NET_ADMIN] + security_opt: [no-new-privileges:true](cap_drop)で,コンテナ内で raw socket を構築できず,権限昇格もできません。Umask=0o077 は daemon モードでファイル権限を保護しますが,コンテナでは事前作成 0700 ディレクトリで同等効果を出します(install -d permissions:312-320)。
  3. 健全性チェック内蔵:HEALTHCHECK(HEALTHCHECK:341-342)が直接 /healthz エンドポイントを呼び,外部プローブスクリプトに依存しません——オーケストレータ(Docker Compose/Fly/Render)はこれでコンテナが生きているか判断します。/healthz は liveness,/readyz は readiness,別名 /health/ready で習慣互換。
  4. オーケストレータ跨ぎ:fly.tomlrender.yamldocker-compose.yml の 3 セット設定で 3 種のデプロイターゲットをカバーしますが,底はすべて同じイメージ——違いはポート/マウント/環境変数宣言方式だけです。

主要ファイル

  • Dockerfile build args:10-25OPENCLAW_EXTENSIONS=""OPENCLAW_BUNDLED_PLUGIN_DIR=extensions、pinned base image digest(node:24-bookworm / bookworm-slim / Bun)。
  • Stage 1: workspace-deps:26-45 — package.json リストだけをコピー,main build layer が無関係ソース変更で invalidate されるのを回避。
  • Stage 2: build:48-99 — COPY Bun binary、corepack enablepnpm install --frozen-lockfile、matrix-sdk-crypto native addon 再試行、pnpm build:docker + pnpm ui:build
  • matrix-sdk-crypto 重试:82-97 — native addon ダウンロードを 5 回再試行,matrix-sdk-crypto-nodejs の CDN が時々不安定のため。
  • runtime-assets:135-154pnpm prune --prod --offline で dev dependencies、d.ts、.map、node_modules/openclaw(自己参照)を削除。
  • Stage 3: runtime:156-189 — base bookworm-slim,装 ca-certificates curl git hostname lsof openssl procps python3 tini
  • ca-certificates install:184-189 — 入れないと HTTPS outbound が TLS handshake で失敗(error setting certificate file)。
  • OPENCLAW_INSTALL_BROWSER:252-262 — オプション Playwright Chromium + Xvfb,毎回起動時に 60-90s install するのを回避。
  • OPENCLAW_INSTALL_DOCKER_CLI:268-301 — オプション Docker CLI,sandbox コンテナ管理に必要;GPG fingerprint 二次検証,ちょうど 1 個の pub key を要求。
  • CLI symlink:304-305ln -sf /app/openclaw.mjs /usr/local/bin/openclaw,非 root npm グローバル書き込みを回避。
  • 预创建目录权限:312-320install -d -m 0700 -o node -g node,それから stat -c で検証,root 所有のディレクトリが named volume 初回マウントを汚染するのを防止。
  • ENV + USER + HEALTHCHECK + CMD:322-344NODE_ENV=productionUSER nodeHEALTHCHECK 3min 間隔 + tini 入口 + CMD ["node","openclaw.mjs","gateway"]
  • fly.tomlapp = "openclaw"primary_region = "iad"internal_port = 3000auto_stop_machines = false(常駐維持)、min_machines_running = 1shared-cpu-2x + 2GB、openclaw_data volume を /data にマウント。
  • render.yamlruntime: dockerplan: starterhealthCheckPath: /healthOPENCLAW_GATEWAY_TOKEN: generateValue: true(自動生成)、1GB disk を /data にマウント。
  • docker-compose.ymlopenclaw-gateway + openclaw-cli 2 サービス,CLI は network_mode: service:openclaw-gateway でネットワーク名前空間を共有,~/.openclaw と secrets ディレクトリをマウント。

データフロー

コンテナ起動の最終数ステップ(Dockerfile 尾部:322-344)が重要です:

dockerfile
ENV NODE_ENV=production

# Security hardening: Run as non-root user
# The node:24-bookworm image includes a 'node' user (uid 1000)
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
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))"
ENTRYPOINT ["tini", "-s", "--"]
CMD ["node", "openclaw.mjs", "gateway"]

ENTRYPOINT ["tini", "-s", "--"] が鍵です——tini は軽量 init で,ゾンビプロセス (zombie process) を reap し,信号を Node プロセスに正しく転送します。コンテナの PID 1 はデフォルトで SIGCHLD を処理しないため,tini がないと ACP/subagent 子プロセス終了後にゾンビ化します;tini はさらに docker stop の SIGTERM を Node に綺麗に転送し,gateway を 10s 後の SIGKILL ではなく綺麗な終了に導きます。

HEALTHCHECK は Node 内蔵 fetch(Node 18+ でグローバル利用可)を使い,curl 不要で直接 /healthz を呼びます。--start-period=15s は gateway bootstrap(プラグインロード、channel 接続)にバッファを与え,3 分 1 回のプローブ頻度は十分タイムリーだが喧騒ではありません。--retries=3 は連続 3 回失敗で初めて unhealthy とマークし,偶発ジッターの誤判定を回避します。

docker-compose.yml の健全性チェックはより積極的:30s 間隔、5 回 retries、20s start_period(compose healthcheck)。compose のコマンドラインはデフォルト --bind lan を上書きし,bridge ネットワークでは loopback bind で host からアクセスできないからです——これは Dockerfile コメントで明示警告されている罠です。compose はさらにコンテナ側の環境変数パスを固定します:

yaml
environment:
  HOME: /home/node
  OPENCLAW_HOME: /home/node
  # Pin container-side state, workspace, and config paths so host values written to
  # `.env` (used by Compose for the bind-mount source below) cannot leak
  # into runtime code that resolves these env vars inside the container.
  # Without this override, a macOS host path like /Users/<you>/.openclaw/...
  # imported from .env caused first-reply `mkdir '/Users'` EACCES failures
  # in Linux Docker (#77436).
  OPENCLAW_STATE_DIR: /home/node/.openclaw
  OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json
  OPENCLAW_CONFIG_DIR: /home/node/.openclaw
  OPENCLAW_WORKSPACE_DIR: /home/node/.openclaw/workspace

この上書きは #77436 を修正します——ユーザが .envOPENCLAW_STATE_DIR=/Users/alex/.openclaw と書くと,compose がそれをコンテナに注入し,コンテナ内の Node が mkdir '/Users' を試みて EACCES になります。だから compose はコンテナ側のこれらのパスを /home/node/.openclaw に強制上書きし,host パスは bind-mount source としてだけ使います。

fly.toml(fly.toml)は同じイメージを Fly.io にデプロイします:

toml
app = "openclaw"
primary_region = "iad"

[build]
dockerfile = "Dockerfile"

[env]
NODE_ENV = "production"
OPENCLAW_PREFER_PNPM = "1"
OPENCLAW_STATE_DIR = "/data"
NODE_OPTIONS = "--max-old-space-size=1536"

[processes]
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan"

[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = false   # 常駐維持
auto_start_machines = true
min_machines_running = 1
processes = ["app"]

[[vm]]
size = "shared-cpu-2x"
memory = "2048mb"

[mounts]
source = "openclaw_data"
destination = "/data"

Fly は internal_port = 3000 で健全性をプローブ(compose の OPENCLAW_GATEWAY_PORT に対応)し,force_https = true で Fly エッジ層が TLS を終端,auto_stop_machines = false + min_machines_running = 1 で WebSocket 長接続が Fly の scale-to-zero で殺されないようにします——これは agent システムデプロイの硬い制約:gateway は必ず常駐し,切れるとすべてのクライアントセッションが落ちます。NODE_OPTIONS = "--max-old-space-size=1536" は 2GB vm メモリと協調し,~500MB を Bun/native addon/Playwright に残し,OOM kill を回避します。

境界と失敗

  • base image SHA pin:OPENCLAW_NODE_BOOKWORM_IMAGE/OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE/OPENCLAW_BUN_IMAGE はすべて SHA256 digest に pin(digest pin:12-17)。Dependabot がこれらの blessed digest を更新します——release ビルドは審査済みスナップショットを消費し,毎回のビルドで最新を引っ張らず,reproducible build を保証します。
  • matrix-sdk-crypto ダウンロード再試行(matrix-sdk-crypto retry:82-97)matrix-sdk-crypto-nodejs の native addon CDN が不安定で,exit 0 でも .node ファイルがダウンロードされないことがあります。コードは 5 回再試行 + sleep $((attempt * 2)) 指数バックオフし,最終失敗時は exit 1 します。OPENCLAW_EXTENSIONSmatrix が含まれる場合だけこのチェックを行います。
  • A2UI クロスアーキ stub(a2ui stub:113-118):pnpm canvas:a2ui:bundle は QEMU クロスアーキビルド(Apple Silicon で amd64 構築)時に失敗することがあり,CI はネイティブ毎アーキビルドなので影響ありません。ここでは stub に fallback(非致命的)し,ローカルクロスビルドが UI bundle で止まるのを回避します。
  • OPENCLAW_PREFER_PNPM=1(OPENCLAW_PREFER_PNPM:124):UI build は Bun ではなく pnpm を使い,Bun は ARM/Synology アーキテクチャに互換問題があるためです。
  • prune --prod --offline(runtime-assets prune:140-154):先に pnpm store add で tarball を seed し,それから --config.offline=true で prune し,prune 時にネット接続しないようにします。prune 後に node scripts/check-package-dist-imports.mjs で import 境界を検証し,prod イメージに dev-only 依存が現れないようにします。
  • 非 root だが /home/node/.config 権限の罠(config dir ownership:309-320):install -d-o node を付けないと /home/node/.config が root:root に作られ,openclaw サブディレクトリが root 所有を継承し,コンテナが node ユーザで走ると書き込めません(#85968)。コードは先に install -d -m 0755 -o node -g node /home/node/.config してからサブディレクトリを作り,stat -c で権限が完全に期待通りか検証します。
  • Docker CLI GPG 単 key 強制(docker gpg single key:283-289):install OPENCLAW_INSTALL_DOCKER_CLI=1 時,Docker apt signing key ダウンロード後に gpg --show-keys --with-colonspub 行を数え,ちょうど 1 個を要求します——多 key ファイルはインポートを拒否し,「apt が多 key ファイルの最初の key を信頼したつもりが実は別の key を信頼していた」を防ぎます。
  • bind-mount パス漏洩(compose env override):host.env にある macOS パスはコンテナに入れられないので,compose は OPENCLAW_STATE_DIR/OPENCLAW_CONFIG_PATH/OPENCLAW_CONFIG_DIR/OPENCLAW_WORKSPACE_DIR の 4 変数を /home/node 以下の固定パスに強制上書きします。bind-mount source は ${HOME}/.openclaw を使い,host 側で指定可能です。
  • Fly 常駐セマンティクス:auto_stop_machines = false + min_machines_running = 1 は agent gateway デプロイの硬い制約——Fly はデフォルトで scale-to-zero でコスト削減しますが,WebSocket 長接続が切れるとすべての IDE/クライアントセッションが失われます。Fly デプロイは明示的に自動停止をオフにしなければなりません。shared-cpu-2x + 2GB は最低合理設定で,これ以下のメモリだと Node が OOM します。
  • Render 自動生成 token:render.yamlOPENCLAW_GATEWAY_TOKEN: generateValue: true が Render プラットフォームに自動生成と注入をさせます——ユーザが yaml に token を硬結合するのを回避します。healthCheckPath: /health/healthz でなく別名を使い,Render ドキュメントがこのパスに慣れているためです。
  • cap_drop: [NET_RAW, NET_ADMIN] + no-new-privileges:コンテナが攻破されても raw socket を構築できず(ポートスキャン/ARP 詐欺防止),setuid で権限昇格できません。これはコンテナ化デプロイの縦深防御 (defense-in-depth) ベースラインで,daemon モードでは到達できない隔離レベルです。

まとめ

コンテナ化デプロイは launchd/systemd/schtasks を Docker Engine/Fly machine/Render に置き換えますが,OpenClaw の責務分割は不変:gateway プロセスは依然 node openclaw.mjs gateway を走らせ(Gateway コア 参照),入口が tini で信号 + ゾンビプロセスを処理し,健全性チェックは内蔵 /healthz//readyz エンドポイントに頼ります。3 種のデプロイターゲット(Docker Compose ローカルセルフホスト / Fly.io クラウド / Render.com クラウド)は同じ Dockerfile を共有し,差はオーケストレータ固有のボリュームマウント、リソース仕様、token 注入方式だけです。物理機や VM デプロイは Docker を必要とせず,Daemon:システムサービス で直接システムサービスとしてインストール可能;設定ファイル自体(openclaw.json)は両デプロイ形態で同じ,openclaw.json を参照。