# healthee — production compose stack (used by deployment.sh on the VPS). # # Two services: # db — TimescaleDB (Postgres + timescaledb extension), data on a named volume # api — FastAPI app behind host-nginx, bound to 127.0.0.1 only # # Run via deployment.sh, or directly: # docker compose -f docker-compose.prod.yml up -d --build # docker compose -f docker-compose.prod.yml exec api healthee init-db # # Env values come from .env in the project root. # (The plain docker-compose.yml stays as the LOCAL DEV DB-only stack.) services: db: image: timescale/timescaledb:latest-pg17 container_name: healthee-db restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_DB: ${POSTGRES_DB} volumes: - healthee-pgdata:/var/lib/postgresql/data # Internal-only — never expose Postgres to the host. The api service talks # to it on the compose-internal network. expose: - "5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 5s timeout: 5s retries: 20 networks: - healthee-net api: build: context: . dockerfile: Dockerfile image: healthee-api:latest container_name: healthee-api restart: unless-stopped depends_on: db: condition: service_healthy environment: # DB connection — service name 'db' is resolvable on the compose network. POSTGRES_HOST: db POSTGRES_PORT: 5432 POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # API auth + LLM. REALTIME_INGEST_TOKEN: ${REALTIME_INGEST_TOKEN} OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-} API_HOST: 0.0.0.0 API_PORT: 8765 # Telegram notifications (optional — silent no-op if blank). TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-} TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-} # Zepp Cloud credentials — when set, auth layer auto-relogins on 401. ZEPP_EMAIL: ${ZEPP_EMAIL:-} ZEPP_PASSWORD: ${ZEPP_PASSWORD:-} volumes: # Uploaded Gadgetbridge DBs land here (and raw_bins eventually). - healthee-gbdata:/app/data/gadgetbridge # Zepp Cloud tokens (~30 day TTL — survive container restarts). - healthee-secrets:/app/.secrets # Bind ONLY to localhost — nginx on the host reverse-proxies to this port. # Never expose 8765 publicly; auth is Bearer-token only. ports: - "127.0.0.1:8765:8765" healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8765/healthz', timeout=2)"] interval: 30s timeout: 5s retries: 3 start_period: 20s networks: - healthee-net scheduler: # In-container cron equivalent. Same image as api, but CMD overridden to # run the Python scheduler that triggers daily-sync (06:30 IST) and the # weekly recompute (Mon 06:00 IST). Lives in src/healthee/scheduler.py. image: healthee-api:latest container_name: healthee-scheduler restart: unless-stopped depends_on: api: condition: service_healthy command: ["python", "-m", "healthee.scheduler"] environment: POSTGRES_HOST: db POSTGRES_PORT: 5432 POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-} TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-} TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-} ZEPP_EMAIL: ${ZEPP_EMAIL:-} ZEPP_PASSWORD: ${ZEPP_PASSWORD:-} volumes: # Shares the same secrets volume so the Zepp token is visible. - healthee-secrets:/app/.secrets networks: - healthee-net volumes: healthee-pgdata: healthee-gbdata: healthee-secrets: networks: healthee-net: driver: bridge