# ============================================================ # SmallClaw / LocalClaw – Dockerfile # ============================================================ # Multi-stage build: # 1. builder – compiles TypeScript → dist/ # 2. runtime – lean production image with Playwright + Tesseract deps # ── Stage 1: Builder ──────────────────────────────────────── FROM node:20-slim AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY tsconfig.json ./ COPY src/ ./src/ RUN npm run build # ── Stage 2: Runtime ──────────────────────────────────────── FROM node:20-slim AS runtime # System deps: Playwright/Chromium + Tesseract OCR + ffmpeg (for voice) RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ curl \ wget \ ffmpeg \ fonts-liberation \ libatk-bridge2.0-0 \ libatk1.0-0 \ libcairo2 \ libcups2 \ libdbus-1-3 \ libdrm2 \ libexpat1 \ libgbm1 \ libglib2.0-0 \ libgtk-3-0 \ libnspr4 \ libnss3 \ libpango-1.0-0 \ libpangocairo-1.0-0 \ libx11-6 \ libx11-xcb1 \ libxcb1 \ libxcomposite1 \ libxdamage1 \ libxext6 \ libxfixes3 \ libxrandr2 \ libxrender1 \ libxss1 \ libxtst6 \ xdg-utils \ tesseract-ocr \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # Production deps only COPY package.json package-lock.json ./ RUN npm ci --omit=dev # Install Playwright browser binaries RUN npx playwright install chromium --with-deps 2>/dev/null || true # Install Piper TTS binary + Korean voice model (from neurlang — rhasspy repo returns 404 for Korean) RUN ARCH=$(dpkg --print-architecture) && \ if [ "$ARCH" = "amd64" ]; then PIPER_ARCH="x86_64"; \ elif [ "$ARCH" = "arm64" ]; then PIPER_ARCH="aarch64"; \ elif [ "$ARCH" = "armhf" ]; then PIPER_ARCH="armv7l"; \ else PIPER_ARCH="$ARCH"; fi && \ cd /tmp && \ curl -L "https://github.com/rhasspy/piper/releases/download/2023.11.14-2/piper_linux_${PIPER_ARCH}.tar.gz" -o piper.tar.gz && \ tar xzf piper.tar.gz && \ cp piper/piper /usr/local/bin/piper && \ chmod +x /usr/local/bin/piper && \ cp piper/libespeak-ng.so* /usr/local/lib/ && \ cp piper/libonnxruntime.so* /usr/local/lib/ && \ cp piper/libpiper_phonemize.so* /usr/local/lib/ && \ cp piper/espeak-ng /usr/local/lib/ && \ cp -r piper/espeak-ng-data /usr/local/lib/ && \ ldconfig && \ rm -rf piper piper.tar.gz && \ mkdir -p /usr/local/share/piper-voices && \ curl -L "https://huggingface.co/neurlang/piper-onnx-kss-korean/resolve/main/piper-kss-korean.onnx" \ -o /usr/local/share/piper-voices/ko_KR-kss-medium.onnx && \ curl -L "https://huggingface.co/neurlang/piper-onnx-kss-korean/raw/main/piper-kss-korean.onnx.json" \ -o /usr/local/share/piper-voices/ko_KR-kss-medium.onnx.json # Install whisper.cpp (build from source — no pre-built Linux binary) RUN apt-get update && apt-get install -y --no-install-recommends cmake git build-essential && \ cd /tmp && \ git clone https://github.com/ggml-org/whisper.cpp.git && \ cd whisper.cpp && \ cmake -B build && \ cmake --build build --config Release -j$(nproc) && \ cp build/bin/whisper-cli /usr/local/bin/whisper-cli && \ rm -rf /tmp/whisper.cpp && \ apt-get remove -y git build-essential && \ apt-get autoremove -y && \ rm -rf /var/lib/apt/lists/* && \ mkdir -p /usr/local/share/whisper-models && \ curl -L "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin" \ -o /usr/local/share/whisper-models/ggml-base.bin # Compiled app from builder COPY --from=builder /app/dist ./dist # Static web UI COPY web-ui/ ./web-ui/ # Data directories (overridden by volumes in compose) RUN mkdir -p /data/workspace /data/logs /root/.localclaw # ── Environment defaults ───────────────────────────────────── # These are overridden by docker-compose.yml / -e flags. # Provider: ollama | lm_studio | llama_cpp | openai | openai_codex ENV NODE_ENV=production \ DOCKER_CONTAINER=true \ SMALLCLAW_DATA_DIR=/data \ SMALLCLAW_WORKSPACE_DIR=/data/workspace \ GATEWAY_PORT=18789 \ GATEWAY_HOST=0.0.0.0 \ PLAYWRIGHT_BROWSERS_PATH=/root/.cache/ms-playwright \ \ # Active provider SMALLCLAW_PROVIDER=ollama \ \ # Ollama OLLAMA_HOST=http://ollama:11434 \ \ # LM Studio (host machine via host.docker.internal) LM_STUDIO_ENDPOINT=http://host.docker.internal:1234 \ LM_STUDIO_API_KEY="" \ LM_STUDIO_MODEL="" \ \ # llama.cpp (host machine via host.docker.internal) LLAMA_CPP_ENDPOINT=http://host.docker.internal:8080 \ LLAMA_CPP_MODEL="" \ \ # OpenAI OPENAI_API_KEY="" \ OPENAI_MODEL=gpt-4o \ \ # OpenAI Codex OAuth (tokens live in mounted ~/.localclaw volume) CODEX_MODEL=gpt-5.3-codex \ \ # Voice (Piper TTS + Whisper STT model paths) PIPER_MODEL_PATH=/usr/local/share/piper-voices/ko_KR-kss-medium.onnx \ PIPER_CONFIG_PATH=/usr/local/share/piper-voices/ko_KR-kss-medium.onnx.json \ WHISPER_MODEL_PATH=/usr/local/share/whisper-models/ggml-base.bin EXPOSE 18789 HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \ CMD curl -f http://localhost:18789/health || exit 1 CMD ["node", "dist/cli/index.js", "gateway"]