feat(docker): image, compose, and env for remote-HTTP deployment

- docker/Dockerfile: multi-stage uv wheel build; runtime runs `familiar serve`
  as a non-root user (uid 1000) on port 8080, /data volume for the encrypted
  token store, and a HEALTHCHECK against /healthz. No token is baked in — serve
  refuses to start without FAMILIAR_HTTP_TOKEN, so it's supplied at run time.
- docker-compose.yml: joins an external `proxy` network (Traefik labels for TLS,
  nginx/Caddy documented as alternatives), read-only rootfs, tmpfs /tmp,
  no-new-privileges, cap_drop ALL, non-root, /healthz healthcheck. Pulls the
  published FAMILIAR_IMAGE with a local build as fallback; writes default off.
- .env.example: every FAMILIAR_* knob incl. the bearer token, auth mode, the
  OAuth resource-server settings, and the insecure-bind escape hatch.
- .dockerignore: keep local state (data/, *.enc, key) and dev files out.
This commit is contained in:
Latte committed 2026-07-04 19:26:17 +02:00
1 parent 7bb99d94bf
commit 39ce8005c0
4 files changed
+211

No files matched your search

+59
View File
@@ -0,0 +1,59 @@
# 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"]