162 lines
5.5 KiB
Docker
162 lines
5.5 KiB
Docker
# ============================================================
|
||
# 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"]
|