deploy: production compose overlay, nginx vhost and deploy script
Three pieces so kadence.timka.su can be stood up and updated from the repo
rather than from shell history on the box:
* `docker-compose.prod.yml` — publishes the app on loopback only. The base
file's `${APP_PORT}:8000` binds every interface, which on this host both
collides with remnawave (already on 8000) and would serve the API beside
the proxy without TLS. Compose *appends* merged port lists, so the override
needs the `!override` tag or the public binding survives. uvicorn also gets
`--proxy-headers`, without which every request looks like plain HTTP from
127.0.0.1 and the session cookie loses its `Secure` flag.
* `deploy/nginx/kadence.timka.su.conf` — TLS, HSTS, the forwarded headers the
cookie logic reads, and a 120s read timeout because one task refresh fans
out to four third-party APIs.
* `deploy/deploy.sh` — idempotent install-or-update. Generates API_TOKEN and
the database password once and never rotates them afterwards (that would
lock out the phone and the desktop app), treats `.env.example`'s
`change-me` placeholders as unset, and uses `certbot certonly --webroot`
rather than `--nginx` so the vhost in git stays the truth.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
4 files changed
+293
-1
No files matched your search
@@ -0,0 +1,54 @@
|
||||
# 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
|
||||
Reference in new issue
Block a user