[Bug]: Docker self-build from main broken — gateway fails to start after channel-to-extension migration
Summary
Building the Docker image from the stock Dockerfile on current main produces a gateway that fails to start. The process launches but errors out during channel and plugin loading before ever reaching a listening state. Two issues in the Dockerfile interact to cause this.
This affects every self-builder using Docker — anyone who runs docker build -f Dockerfile . or uses docker-setup.sh on current main. The next tagged release (v2026.3.14+) will carry these issues unless fixed.
The official GHCR image for v2026.3.13 is not affected because the channel-to-extension migration had not yet landed at that tag.
Symptoms
Depending on which issue hits first, the gateway logs one or more of:
[plugins] whatsapp failed to load from /app/extensions/whatsapp/index.ts:
Error: Cannot find module '../../../src/channels/plugins/account-helpers.js'
Cannot find package 'openclaw' imported from /app/dist/...
(A third symptom — Error: Unable to resolve plugin runtime module — was independently fixed on main via the buildUnifiedDistEntries() refactor that added "plugins/runtime/index" to buildCoreDistEntries(). See #48422.)
Root cause
The channel-to-extension migration (PRs #45725, #45967) moved all channels into extensions/. These extensions now contain hundreds of relative ../../../src/ imports that the tsdown bundler resolves at build time. Two issues in the Dockerfile prevent this from working:
1. OPENCLAW_EXTENSIONS defaults to empty — extensions don't get compiled
| Commit |
Description |
16505718e8 |
Move WhatsApp to extensions/ (#45725) |
439c21e078 |
Remove channel shim dirs, point imports to extensions (#45967) |
57f19f0d5c |
Add OPENCLAW_EXTENSIONS build arg (#32223) |
The Dockerfile ARG OPENCLAW_EXTENSIONS="" means the ext-deps stage copies zero package.json files. Extension npm deps are never installed, so the tsdown bundler silently produces zero dist/extensions/ output. The gateway falls back to Jiti transpilation of raw .ts from extensions/, which fails because /app/src/ is excluded from the runtime image and the extensions use ../../../src/ relative imports.
Compounding this, docker-setup.sh always passes --build-arg "OPENCLAW_EXTENSIONS=${OPENCLAW_EXTENSIONS}". When the host env var is unset, this sends an empty string to the build — overriding whatever the Dockerfile ARG default is. So even changing the default to a non-empty value isn't sufficient; the fix must also treat empty string as "build all."
2. pnpm prune --prod removes the self-referencing symlink
| Commit |
Description |
b46ac250d1 |
WhatsApp: use scoped plugin SDK imports |
e9cf3506fd |
Telegram: use scoped plugin SDK imports |
After the v2026.3.7 scoped-import migration, plugins import from openclaw/plugin-sdk/* subpaths which require node_modules/openclaw to resolve. The Dockerfile's pnpm prune --prod step removes this self-referencing link, breaking all SDK subpath imports at runtime.
Reproduction
git clone https://github.com/openclaw/openclaw.git
cd openclaw
docker build -t openclaw:local -f Dockerfile .
# Verify extensions weren't compiled:
docker run --rm openclaw:local sh -c 'ls /app/dist/extensions/ 2>/dev/null | wc -l'
# Expected: 0
# Verify symlink missing:
docker run --rm openclaw:local ls -la /app/node_modules/openclaw 2>/dev/null
# Expected: not found
# Gateway fails to start:
docker run --rm openclaw:local timeout 15 node openclaw.mjs gateway \
--allow-unconfigured 2>&1 | grep -iE "error|fail" | head -5
Comparison: official GHCR image vs self-built from main
| Check |
Official (v2026.3.13) |
Self-built from main |
dist/extensions/ |
Does not exist |
Does not exist (without OPENCLAW_EXTENSIONS) |
node_modules/openclaw |
Does not exist |
Does not exist |
/app/src/ |
Does not exist |
Does not exist |
extensions/whatsapp/src/accounts.ts |
Does not exist |
Exists (new on main) |
| Gateway starts |
Yes (Jiti + self-ref exports) |
No (relative imports fail) |
The official image works because extensions/whatsapp/src/accounts.ts doesn't exist in v2026.3.13 — the file was added on main by commit 16505718e8 with relative ../../../src/ imports. Hundreds of similar imports exist across all extensions post-migration.
Fix
PR #48523 addresses both issues (Dockerfile only):
- Auto-detect extensions — Default
OPENCLAW_EXTENSIONS to "__all__" and treat empty string the same way, so both docker build . and docker-setup.sh produce working builds.
- Restore symlink — Add
RUN ln -s /app /app/node_modules/openclaw after pnpm prune --prod.
Related
Environment
- OpenClaw:
main (post v2026.3.13, pre v2026.3.14)
- Node: 24
- Docker: building from stock
Dockerfile
- OS: Ubuntu 24.04 (VPS)
[Bug]: Docker self-build from
mainbroken — gateway fails to start after channel-to-extension migrationSummary
Building the Docker image from the stock
Dockerfileon currentmainproduces a gateway that fails to start. The process launches but errors out during channel and plugin loading before ever reaching a listening state. Two issues in the Dockerfile interact to cause this.This affects every self-builder using Docker — anyone who runs
docker build -f Dockerfile .or usesdocker-setup.shon currentmain. The next tagged release (v2026.3.14+) will carry these issues unless fixed.The official GHCR image for v2026.3.13 is not affected because the channel-to-extension migration had not yet landed at that tag.
Symptoms
Depending on which issue hits first, the gateway logs one or more of:
(A third symptom —
Error: Unable to resolve plugin runtime module— was independently fixed onmainvia thebuildUnifiedDistEntries()refactor that added"plugins/runtime/index"tobuildCoreDistEntries(). See #48422.)Root cause
The channel-to-extension migration (PRs #45725, #45967) moved all channels into
extensions/. These extensions now contain hundreds of relative../../../src/imports that the tsdown bundler resolves at build time. Two issues in the Dockerfile prevent this from working:1.
OPENCLAW_EXTENSIONSdefaults to empty — extensions don't get compiled16505718e8extensions/(#45725)439c21e07857f19f0d5cOPENCLAW_EXTENSIONSbuild arg (#32223)The Dockerfile
ARG OPENCLAW_EXTENSIONS=""means the ext-deps stage copies zeropackage.jsonfiles. Extension npm deps are never installed, so the tsdown bundler silently produces zerodist/extensions/output. The gateway falls back to Jiti transpilation of raw.tsfromextensions/, which fails because/app/src/is excluded from the runtime image and the extensions use../../../src/relative imports.Compounding this,
docker-setup.shalways passes--build-arg "OPENCLAW_EXTENSIONS=${OPENCLAW_EXTENSIONS}". When the host env var is unset, this sends an empty string to the build — overriding whatever the Dockerfile ARG default is. So even changing the default to a non-empty value isn't sufficient; the fix must also treat empty string as "build all."2.
pnpm prune --prodremoves the self-referencing symlinkb46ac250d1e9cf3506fdAfter the v2026.3.7 scoped-import migration, plugins import from
openclaw/plugin-sdk/*subpaths which requirenode_modules/openclawto resolve. The Dockerfile'spnpm prune --prodstep removes this self-referencing link, breaking all SDK subpath imports at runtime.Reproduction
Comparison: official GHCR image vs self-built from main
dist/extensions/OPENCLAW_EXTENSIONS)node_modules/openclaw/app/src/extensions/whatsapp/src/accounts.tsThe official image works because
extensions/whatsapp/src/accounts.tsdoesn't exist in v2026.3.13 — the file was added onmainby commit16505718e8with relative../../../src/imports. Hundreds of similar imports exist across all extensions post-migration.Fix
PR #48523 addresses both issues (Dockerfile only):
OPENCLAW_EXTENSIONSto"__all__"and treat empty string the same way, so bothdocker build .anddocker-setup.shproduce working builds.RUN ln -s /app /app/node_modules/openclawafterpnpm prune --prod.Related
../../../src/import pattern: [Bug]: llm-task: dynamic import fails for pi-embedded-runner.js (source and dist agents missing, only extensionAPI.js works) #18846, llm-task plugin: Cannot find module pi-embedded-runner.js #4056, [Bug]: nextcloud-talk failed to load from /root/.openclaw/extensions/nextcloud-talk/index.ts: Error: Cannot find module '../../../src/infra/abort-signal.js #32662, [Bug]: nextcloud-talk plugin fails to load: Cannot find module '../../../src/infra/abort-signal.js' #37915Environment
main(post v2026.3.13, pre v2026.3.14)Dockerfile