# niche-finder -- production overrides for a hosted copy (multi-user plan P1-13) # # Layered on top of docker-compose.yml, never used alone: # make prod-up # # = docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build \ # # postgres web worker mcp-http proxy backup # # What changes compared with a copy on your own machine (docs/hosting.md): # - Caddy (`proxy`) is the only thing published: ports 80 and 443 on # NF_DOMAIN, with a Let's Encrypt certificate it renews by itself. The # dashboard (8080) and Postgres (5433) are no longer published at all, # not even on 127.0.0.1. # - Accounts are on (NF_MULTI_USER=1), the sign-in cookie is Secure, the API # answers to NF_DOMAIN, and the request and sign-in limits count each # client by X-Forwarded-For from the proxy (NF_TRUSTED_PROXIES). # - MCP over HTTP runs all the time behind the same domain, at /mcp, and # accepts only personal API tokens. # - Logs are one JSON object per line (NF_LOG_FORMAT); /metrics on web # opens with NF_METRICS_TOKEN, for a Prometheus on the compose network. # - `backup` dumps the database every BACKUP_INTERVAL_HOURS into # BACKUP_DIR, checks that each dump restores, and keeps the newest # BACKUP_KEEP. # # Required in .env: NF_DOMAIN, POSTGRES_PASSWORD (compose refuses to start # without them), YOUTUBE_API_KEY, OWN_TOKENS_KEY. Recommended: # POSTGRES_APP_PASSWORD -- web and mcp-http then serve people's requests with a # database login that Row-Level Security holds (it reaches them via env_file; # the services set it up at start, docs/hosting.md). The checklist is in # SECURITY.md, "What you have to do before you give anyone an account". services: postgres: ports: !reset [] environment: POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?set a strong POSTGRES_PASSWORD in .env}" web: ports: !reset [] environment: NF_MULTI_USER: "1" NF_COOKIE_SECURE: "1" # NF_DOMAIN plus any extra names from .env's NF_ALLOWED_HOSTS (`web`, # how Prometheus reaches /metrics inside compose, is always allowed) NF_ALLOWED_HOSTS: "${NF_DOMAIN:?set NF_DOMAIN in .env, e.g. NF_DOMAIN=niches.example.com},${NF_ALLOWED_HOSTS:-}" NF_LOG_FORMAT: "${NF_LOG_FORMAT:-json}" # Only containers on this compose network can reach web:8080 (the port # is not published), so trusting Docker's private ranges means trusting # the proxy. Never '*'. NF_TRUSTED_PROXIES: "${NF_TRUSTED_PROXIES:-172.16.0.0/12,192.168.0.0/16}" worker: environment: NF_MULTI_USER: "1" NF_LOG_FORMAT: "${NF_LOG_FORMAT:-json}" mcp-http: profiles: !reset [] environment: NF_MULTI_USER: "1" NF_LOG_FORMAT: "${NF_LOG_FORMAT:-json}" # tokens are required here; never start without them (P1-15) MCP_ALLOW_UNAUTHENTICATED: "0" MCP_PUBLIC_URL: "https://${NF_DOMAIN:?set NF_DOMAIN in .env}" proxy: image: caddy:2-alpine container_name: niche-finder-proxy restart: unless-stopped depends_on: - web - mcp-http environment: NF_DOMAIN: "${NF_DOMAIN:?set NF_DOMAIN in .env}" volumes: - ./infra/caddy/Caddyfile.prod:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config ports: - "80:80" - "443:443" - "443:443/udp" backup: # the database's own image, so pg_dump always matches the server version image: pgvector/pgvector:pg16 container_name: niche-finder-backup restart: unless-stopped depends_on: postgres: condition: service_healthy entrypoint: ["sh", "/backup.sh"] environment: PGHOST: postgres PGUSER: "${POSTGRES_USER:-niches}" PGPASSWORD: "${POSTGRES_PASSWORD:?set a strong POSTGRES_PASSWORD in .env}" PGDATABASE: "${POSTGRES_DB:-niches}" BACKUP_INTERVAL_HOURS: "${BACKUP_INTERVAL_HOURS:-24}" BACKUP_KEEP: "${BACKUP_KEEP:-14}" BACKUP_VERIFY: "${BACKUP_VERIFY:-1}" TZ: "${TZ:-Asia/Tashkent}" volumes: - ./infra/backup/backup.sh:/backup.sh:ro # a host folder, so the dumps are easy to copy off the server - "${BACKUP_DIR:-./backups}:/backups"