diff --git a/docs/guides/DOCKER_GUIDE.md b/docs/guides/DOCKER_GUIDE.md index 5233889912..b1e6968cfc 100644 --- a/docs/guides/DOCKER_GUIDE.md +++ b/docs/guides/DOCKER_GUIDE.md @@ -147,11 +147,11 @@ The prod stack runs in parallel with the dev compose (different container names, The repository ships a multi-stage Dockerfile (`Dockerfile`). Three stages are exposed; pick the right `target` for your use case. -| Stage | Base image | Purpose | -| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `builder` | `node:24.15.0-trixie-slim` | Installs deps (`npm ci --legacy-peer-deps`) and runs `npm run build -- --webpack` | -| `runner-base` | `node:24.15.0-trixie-slim` | Production runtime with the Next.js standalone output. **No provider CLIs bundled.** | -| `runner-cli` | `runner-base` | Adds `git`, `docker.io`, `docker-compose` and global CLIs: `@openai/codex`, `@anthropic-ai/claude-code`, `droid`, `openclaw`. **Pick this for agentic workflows.** | +| Stage | Base image | Purpose | +| ------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `builder` | `node:26-trixie-slim` | Installs deps (`npm ci --legacy-peer-deps`) and runs `npm run build` (Turbopack by default — see Build-time resources below) | +| `runner-base` | `node:26-trixie-slim` | Production runtime with the Next.js standalone output. **No provider CLIs bundled.** | +| `runner-cli` | `runner-base` | Adds `git`, `docker.io`, `docker-compose` and global CLIs: `@openai/codex`, `@anthropic-ai/claude-code`, `droid`, `openclaw`. **Pick this for agentic workflows.** | Build a specific target manually: @@ -160,6 +160,42 @@ docker build --target runner-base -t omniroute:base . docker build --target runner-cli -t omniroute:cli . ``` +### Build-time resources + +Two build args control what the `builder` stage costs. They are build-time only — +`OMNIROUTE_MEMORY_MB` (below) is a separate, runtime knob. + +| Build arg | Default | Effect | +| --------------------------- | ------- | ---------------------------------------------------------------------- | +| `OMNIROUTE_USE_TURBOPACK` | `1` | `0` builds with webpack instead. Lower peak memory, slower. | +| `OMNIROUTE_BUILD_MEMORY_MB` | `4096` | V8 heap ceiling (`--max-old-space-size`) for the spawned `next build`. | + +Turbopack compiles in native Rust memory that lives **outside** the V8 heap, so +`OMNIROUTE_BUILD_MEMORY_MB` does not bound it. On a host with a memory ceiling the +build is then SIGKILLed by the OOM killer with no error text at all — it simply +stops mid-`Creating an optimized production build`, which reads like a hang rather +than an out-of-memory. If the build host is constrained, switch bundlers: + +```bash +docker build --target runner-base \ + --build-arg OMNIROUTE_USE_TURBOPACK=0 \ + -t omniroute:base . +``` + +`webpackBuildWorker` is enabled, so `next build` runs a parent **and** a worker +process and each honours `OMNIROUTE_BUILD_MEMORY_MB` separately. Size the container +ceiling above roughly twice that value, not once. + +Measured on this tree (`--target runner-base`, `OMNIROUTE_BUILD_MEMORY_MB=6144`): + +| Bundler | Container ceiling | Result | +| --------- | ----------------- | ----------------------------- | +| Turbopack | 8 GiB / 16 GiB | OOM-killed at both, silently | +| webpack | 8 GiB | build worker SIGKILLed | +| webpack | 12 GiB | succeeded, peaked at 11.1 GiB | + +### Runtime defaults + Defaults exported by `runner-base`: `PORT=20128`, `HOSTNAME=0.0.0.0`, `NODE_OPTIONS=--max-old-space-size=512`, `DATA_DIR=/app/data`, `OMNIROUTE_MIGRATIONS_DIR=/app/migrations`. Memory behavior in Docker: