# Production overlay: TLS and the public port belong to the host's nginx, so the # container publishes nothing to the world. # # docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build # # Two things differ from the base file and both matter once the box has a public # address: # # * the app binds **127.0.0.1 only**. The base file's `${APP_PORT}:8000` listens on # every interface, which would put the API on `95.85.227.62:8000` beside the # reverse proxy and let anyone skip TLS (and, on this host, 8000 is already taken). # * Postgres publishes nothing at all — it is reachable on the compose network and # nowhere else. # # `KADENCE_BIND` exists so a second instance can move off the port without editing # this file. nginx proxies to whatever it says. services: db: # Nothing published: the base file already keeps Postgres on the compose # network only, and this is the file that would be tempted to open it. logging: &json-file driver: json-file options: max-size: "10m" max-file: "5" app: # `!override`, not a plain list: compose *appends* merged port lists, so without # it the base file's `0.0.0.0:8000` would survive alongside this one and the API # would still be on the public interface. Verify with # `docker compose -f docker-compose.yml -f docker-compose.prod.yml config`. ports: !override - "${KADENCE_BIND:-127.0.0.1:8010}:8000" environment: # Same-origin in the browser, so CORS never fires for the web UI; naming the # origin rather than `*` is still correct, because `allow_credentials=True` # plus a `*` origin is a combination browsers refuse outright. CORS_ORIGINS: ${CORS_ORIGINS:-https://kadence.timka.su} # uvicorn must trust the proxy's X-Forwarded-* or every request looks like # plain HTTP from 127.0.0.1 — which would drop `Secure` from the session # cookie and log the wrong client IP for the login rate limiter. FORWARDED_ALLOW_IPS: 127.0.0.1 command: - uvicorn - app.main:app - --host - 0.0.0.0 - --port - "8000" - --proxy-headers - --forwarded-allow-ips - 127.0.0.1 logging: *json-file