Files
familiar/docker-compose.yml
T
Latte 39ce8005c0 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.
2026-07-04 19:26:17 +02:00

81 lines
3.3 KiB
YAML

services:
familiar:
# Pull the image published to Gitea by .gitea/workflows/docker.yml, or build
# locally with `docker compose build`. Pick the channel via .env:
# FAMILIAR_IMAGE (:latest / :main for prod, :dev for the dev channel).
image: ${FAMILIAR_IMAGE:-git.hiddenden.cafe/hiddenden/familiar:latest}
build:
context: .
dockerfile: docker/Dockerfile
container_name: familiar
restart: unless-stopped
env_file:
- .env
environment:
FAMILIAR_HTTP_HOST: 0.0.0.0
FAMILIAR_HTTP_PORT: "8080"
FAMILIAR_HTTP_PATH: ${FAMILIAR_HTTP_PATH:-/mcp}
FAMILIAR_DATA_DIR: /data
# bearer (default) or oauth. In bearer mode FAMILIAR_HTTP_TOKEN is required.
FAMILIAR_AUTH_MODE: ${FAMILIAR_AUTH_MODE:-bearer}
# Bearer token clients send as `Authorization: Bearer <token>`. REQUIRED in
# bearer mode — `familiar serve` refuses to start on 0.0.0.0 without it
# (unless FAMILIAR_ALLOW_INSECURE_BIND=true). Leave blank for oauth mode.
FAMILIAR_HTTP_TOKEN: ${FAMILIAR_HTTP_TOKEN:-}
# OAuth 2.1 resource-server settings (only used when AUTH_MODE=oauth).
FAMILIAR_OAUTH_ISSUER: ${FAMILIAR_OAUTH_ISSUER:-}
FAMILIAR_OAUTH_RESOURCE: ${FAMILIAR_OAUTH_RESOURCE:-}
FAMILIAR_OAUTH_AUDIENCE: ${FAMILIAR_OAUTH_AUDIENCE:-}
FAMILIAR_OAUTH_JWKS_URI: ${FAMILIAR_OAUTH_JWKS_URI:-}
# Writes are off by default over HTTP; set true in .env to allow them.
FAMILIAR_ENABLE_WRITES: ${FAMILIAR_ENABLE_WRITES:-false}
# The encrypted token store (token.enc + key), audit log, and write toggle
# live under /data. Run `familiar login` (or `login --no-browser`) once, then
# point this at that data dir (or copy token.enc + key into ./data).
volumes:
- ${FAMILIAR_DATA_DIR_HOST:-./data}:/data
# Not published to the host — only reachable on the internal proxy network,
# where your TLS reverse proxy terminates HTTPS and forwards to :8080.
expose:
- "8080"
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
user: "1000:1000"
networks:
- proxy
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import httpx; httpx.get('http://127.0.0.1:8080/healthz', timeout=5).raise_for_status()",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
labels:
# --- Traefik TLS termination (Let's Encrypt) -------------------------- #
# Enable if your reverse proxy is Traefik on the external `proxy` network.
# Set FAMILIAR_DOMAIN and TRAEFIK_CERTRESOLVER in .env. For nginx/Caddy
# instead, ignore these labels — see doc/deployment.md.
traefik.enable: "true"
traefik.docker.network: proxy
traefik.http.routers.familiar.rule: Host(`${FAMILIAR_DOMAIN:-familiar.example.com}`)
traefik.http.routers.familiar.entrypoints: websecure
traefik.http.routers.familiar.tls: "true"
traefik.http.routers.familiar.tls.certresolver: ${TRAEFIK_CERTRESOLVER:-letsencrypt}
traefik.http.services.familiar.loadbalancer.server.port: "8080"
networks:
# Shared reverse-proxy network (Traefik/Caddy). Create once with:
# docker network create proxy
proxy:
external: true