FROM python:3.14-slim # System deps. tmux is required by Cookbook for background downloads/serves. # openssh-client is required for Cookbook remote server tests, setup, probes, # downloads, and serves from Docker installs. # git/cmake are required when Cookbook builds llama.cpp on first llama.cpp # launch inside Docker. # nodejs/npm provide npx for the optional built-in Browser MCP server. # gosu lets the entrypoint drop privileges cleanly so signals still reach # uvicorn directly (no extra shell layer like `su`/`sudo` would add). RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ cmake \ curl \ git \ nodejs \ npm \ tmux \ openssh-client \ gosu \ && rm -rf /var/lib/apt/lists/* # Docker CLI (client only — daemon stays on the host via the # /var/run/docker.sock mount). The Debian `docker.io` package ships # dockerd but not the client binary on slim, so grab the static client # tarball from download.docker.com instead. ARG DOCKER_CLI_VERSION=27.5.1 RUN ARCH="$(dpkg --print-architecture)" \ && case "$ARCH" in \ amd64) DARCH=x86_64 ;; \ arm64) DARCH=aarch64 ;; \ *) echo "unsupported arch $ARCH"; exit 1 ;; \ esac \ && curl -fsSL "https://download.docker.com/linux/static/stable/${DARCH}/docker-${DOCKER_CLI_VERSION}.tgz" \ -o /tmp/docker.tgz \ && tar -xzf /tmp/docker.tgz -C /tmp \ && install -m 0755 /tmp/docker/docker /usr/local/bin/docker \ && rm -rf /tmp/docker /tmp/docker.tgz WORKDIR /app # Install Python deps first (layer cache). Optional extras (PyMuPDF AGPL, etc.) # are opt-in so the default image stays MIT-core; see requirements-optional.txt. ARG INSTALL_OPTIONAL=false COPY requirements.txt requirements-optional.txt ./ RUN pip install --no-cache-dir -r requirements.txt \ && if [ "$INSTALL_OPTIONAL" = "true" ]; then pip install --no-cache-dir -r requirements-optional.txt; fi # Copy app code COPY . . # Create data directory (mount a volume here for persistence) RUN mkdir -p data logs services/cache/search # Entrypoint that drops to PUID/PGID (default 1000:1000) and repairs # ownership on the bind-mounted /app/data and /app/logs. Without this, # the container runs as root and writes root-owned files into host # bind mounts — any later non-root run (or a host user trying to # update them) silently fails on EPERM, breaking skill extraction, # prefs persistence, mail attachments, etc. COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh EXPOSE 7000 ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7000"]