# syntax=docker/dockerfile:1 # --------------------------------------------------------------------------- # # Build stage: build a wheel from the source tree with uv. # --------------------------------------------------------------------------- # FROM python:3.12-slim AS builder ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 WORKDIR /app RUN pip install --no-cache-dir uv # Files needed to build the wheel (pyproject references README.md + LICENSE). COPY pyproject.toml README.md LICENSE ./ COPY src ./src RUN uv build --wheel --out-dir /dist # --------------------------------------------------------------------------- # # Runtime stage: minimal, non-root image that runs `familiar serve` (HTTP). # --------------------------------------------------------------------------- # FROM python:3.12-slim ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ # Bind on all interfaces inside the container; a TLS reverse proxy fronts it. FAMILIAR_HTTP_HOST=0.0.0.0 \ FAMILIAR_HTTP_PORT=8080 \ FAMILIAR_HTTP_PATH=/mcp \ # Token store + audit log + write toggle live here — mount a persistent volume. FAMILIAR_DATA_DIR=/data # FAMILIAR_HTTP_TOKEN is intentionally NOT baked in: `familiar serve` refuses # to start without it, so it must be supplied at run time (docker run -e ...). # Writes stay off by default over HTTP; set FAMILIAR_ENABLE_WRITES=true to opt in. WORKDIR /app # Non-root runtime user; /data owned by it so token refresh can write. RUN useradd -m -u 1000 -s /usr/sbin/nologin familiar \ && mkdir -p /data \ && chown -R familiar:familiar /data COPY --from=builder /dist/*.whl /tmp/ RUN pip install --no-cache-dir /tmp/*.whl && rm -f /tmp/*.whl USER familiar EXPOSE 8080 VOLUME ["/data"] # Liveness against the unauthenticated /healthz route (no token needed). HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ CMD python -c "import os,httpx; httpx.get(f\"http://127.0.0.1:{os.environ.get('FAMILIAR_HTTP_PORT','8080')}/healthz\", timeout=5).raise_for_status()" || exit 1 CMD ["familiar", "serve"]