Replace Traefik with host-webserver reverse proxy in prod deployment
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
CI / lint (push) Canceled after 0s
CI / typecheck (push) Canceled after 0s
CI / unit-tests (push) Canceled after 0s
CI / integration-tests (push) Canceled after 0s
CI / container-build (push) Canceled after 0s
CI / dependency-audit (push) Canceled after 0s
The production host runs cPanel, which already owns ports 80/443 for other sites on the box -- there's no Traefik there. docker-compose.prod.yml now binds the api container to 127.0.0.1:8000 only; the host's own Apache/LiteSpeed (via cPanel) reverse-proxies api.amdbs.net to it and terminates TLS itself (AutoSSL). Dropped the external traefik-public network requirement and Traefik labels entirely. Also corrected a separate latent inaccuracy while touching this: a bare `docker compose up` (not `docker stack deploy`) never actually honored deploy.replicas -- docs now say so and point at `--scale` instead of implying free multi-replica scaling that was never really happening. README's Production deployment section rewritten with concrete steps: Cloudflare DNS record, cPanel subdomain + reverse proxy, then compose up. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
ec9b1c28e3
commit
905cd202eb
@@ -12,8 +12,8 @@ build specification this repository implements, and
|
||||
|
||||
## Architecture at a glance
|
||||
|
||||
A modular monolith (not microservices): a stateless FastAPI API behind
|
||||
Traefik, PostgreSQL as the sole source of truth, Redis for disposable
|
||||
A modular monolith (not microservices): a stateless FastAPI API behind a
|
||||
reverse proxy, PostgreSQL as the sole source of truth, Redis for disposable
|
||||
caching/rate-limiting/queueing, and arq workers for async metadata
|
||||
ingestion. Full diagram and reasoning in
|
||||
[`docs/architecture.md`](docs/architecture.md).
|
||||
@@ -170,28 +170,40 @@ example. Concurrency per worker process is controlled by
|
||||
|
||||
## Production deployment
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml up --build -d
|
||||
```
|
||||
The production host runs cPanel, which already owns ports 80/443 for other
|
||||
sites on the box — there's no Traefik here. `docker-compose.prod.yml` binds
|
||||
the API container to `127.0.0.1:8000` only; the host's own webserver
|
||||
(Apache or LiteSpeed, via cPanel) reverse-proxies `api.amdbs.net` to it and
|
||||
terminates TLS itself (cPanel's AutoSSL can issue the Let's Encrypt cert
|
||||
once the subdomain exists and DNS resolves).
|
||||
|
||||
Traefik-ready (labels on the `api` service), PgBouncer in front of
|
||||
PostgreSQL (transaction pooling — `DATABASE_BEHIND_TRANSACTION_POOLER=true`
|
||||
disables asyncpg's server-side prepared statements, which don't survive
|
||||
transaction-mode pooling), horizontally-scaled API and worker replicas. See
|
||||
1. **DNS** (Cloudflare, where `amdbs.net` is managed): add an A record for
|
||||
`api.amdbs.net` pointing at the server's IP.
|
||||
2. **cPanel**: create the `api.amdbs.net` subdomain so AutoSSL picks it up
|
||||
and issues a cert; then reverse-proxy that vhost to `127.0.0.1:8000` —
|
||||
either cPanel/WHM's Application Manager if available, or a custom
|
||||
Apache/LiteSpeed vhost include (`ProxyPass`/`ProxyPassReverse` for
|
||||
Apache; LiteSpeed's External App + rewrite rule for LiteSpeed).
|
||||
3. **`.env`** on the server (never committed), beyond the base vars in
|
||||
`.env.example`:
|
||||
|
||||
```bash
|
||||
CORS_ORIGINS=https://www.amdbs.net # must include the frontend's real origin
|
||||
```
|
||||
|
||||
4. **Deploy**:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml up --build -d
|
||||
```
|
||||
|
||||
PgBouncer sits in front of PostgreSQL (transaction pooling —
|
||||
`DATABASE_BEHIND_TRANSACTION_POOLER=true` disables asyncpg's server-side
|
||||
prepared statements, which don't survive transaction-mode pooling). See
|
||||
[`docs/architecture.md`](docs/architecture.md).
|
||||
|
||||
Requires an external Traefik instance already running with a
|
||||
`traefik-public` Docker network and a `letsencrypt` cert resolver
|
||||
configured (`docker-compose.prod.yml` attaches to it, it doesn't run
|
||||
Traefik itself). Aurora Core's production domain split is `api.amdbs.net`
|
||||
(this backend) / `www.amdbs.net` (frontend, separate project) — the
|
||||
production `.env` (never committed) needs, beyond the base vars in
|
||||
`.env.example`:
|
||||
|
||||
```bash
|
||||
AURORA_API_HOST=api.amdbs.net # Traefik router rule (docker-compose.prod.yml only, not read by the app)
|
||||
CORS_ORIGINS=https://www.amdbs.net # must include the frontend's real origin
|
||||
```
|
||||
Aurora Core's production domain split is `api.amdbs.net` (this backend) /
|
||||
`www.amdbs.net` (frontend, a separate project).
|
||||
|
||||
## Known limitations
|
||||
|
||||
|
||||
+12
-16
@@ -1,12 +1,12 @@
|
||||
# Production-oriented topology (section 1/28): Traefik -> N stateless API
|
||||
# replicas -> PgBouncer -> PostgreSQL, plus Redis and independently-scaled
|
||||
# workers. Requires a pre-existing external Traefik network named
|
||||
# `traefik-public` and a populated `.env` (never commit real secrets).
|
||||
# Production-oriented topology (section 1/28): the host's own webserver
|
||||
# (cPanel/Apache or LiteSpeed -- already holding ports 80/443 for other
|
||||
# sites on the box) reverse-proxies to the API container on 127.0.0.1,
|
||||
# terminating TLS itself; there's no Traefik here. PgBouncer sits in front
|
||||
# of PostgreSQL, Redis and the worker are independently scaled. Requires a
|
||||
# populated `.env` (never commit real secrets).
|
||||
name: aurora-core
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
aurora-internal:
|
||||
driver: bridge
|
||||
|
||||
@@ -97,19 +97,15 @@ services:
|
||||
condition: service_healthy
|
||||
migrate:
|
||||
condition: service_completed_successfully
|
||||
networks: [traefik-public, aurora-internal]
|
||||
networks: [aurora-internal]
|
||||
# Bound to localhost only -- the host's own webserver (cPanel/Apache or
|
||||
# LiteSpeed, already holding 80/443 for other sites on the box) terminates
|
||||
# TLS for api.amdbs.net and reverse-proxies here. No Traefik on this box.
|
||||
ports:
|
||||
- "127.0.0.1:${AURORA_API_PORT:-8000}:8000"
|
||||
deploy:
|
||||
replicas: 3
|
||||
resources:
|
||||
limits: { cpus: "1", memory: 1G }
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.docker.network=traefik-public"
|
||||
- "traefik.http.routers.aurora-core-api.rule=Host(`${AURORA_API_HOST:-api.aurora.local}`)"
|
||||
- "traefik.http.routers.aurora-core-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.aurora-core-api.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.aurora-core-api.loadbalancer.server.port=8000"
|
||||
- "traefik.http.services.aurora-core-api.loadbalancer.healthcheck.path=/health"
|
||||
|
||||
worker:
|
||||
build:
|
||||
|
||||
+4
-2
@@ -198,8 +198,10 @@ of actually ingesting that movie or series, so there's no separate
|
||||
## Administrative (section 17: "separately protected")
|
||||
|
||||
Require header `X-API-Key: <one of AURORA_ADMIN_API_KEYS>`. Never exposed to
|
||||
public clients — mount behind a separate Traefik router/ACL in production if
|
||||
you want network-level isolation too.
|
||||
public clients — add a rule in the host webserver's vhost config for
|
||||
`api.amdbs.net` (e.g. an Apache `<Location>`/LiteSpeed rewrite blocking
|
||||
`/api/v1/admin/*` from outside a trusted IP range) in production if you
|
||||
want network-level isolation on top of the API key.
|
||||
|
||||
| Method | Path | Body | Notes |
|
||||
|---|---|---|---|
|
||||
|
||||
+10
-8
@@ -9,7 +9,7 @@ Aurora applications are consumers of it, never dependencies of it.
|
||||
```
|
||||
Internet
|
||||
|
|
||||
Traefik
|
||||
Host webserver (cPanel-managed Apache/LiteSpeed — TLS termination + reverse proxy)
|
||||
|
|
||||
Aurora Core API replicas (stateless)
|
||||
|
|
||||
@@ -191,8 +191,8 @@ development requires the observability stack to be running.
|
||||
`GET /health` — liveness only (process is up). `GET /ready` — checks
|
||||
PostgreSQL connectivity (always required) and Redis (required only if
|
||||
`REDIS_REQUIRED=true`, since Redis is disposable — see `docs/database.md`).
|
||||
Both are mounted twice: unprefixed (`/health`, `/ready`, for
|
||||
Docker/Traefik health checks) and under `/api/v1` (per section 17's endpoint
|
||||
Both are mounted twice: unprefixed (`/health`, `/ready`, for Docker/host
|
||||
webserver health checks) and under `/api/v1` (per section 17's endpoint
|
||||
list).
|
||||
|
||||
## Statelessness & horizontal scaling (section 1/2)
|
||||
@@ -200,8 +200,10 @@ list).
|
||||
The API holds no in-process state across requests — no server-side
|
||||
sessions, no in-memory caches keyed by request. Every dependency
|
||||
(`get_db`, `get_redis`, `get_queue`) is request-scoped and backed by a
|
||||
shared pool. This is what makes `docker-compose.prod.yml`'s `api: replicas:
|
||||
3` behind Traefik correct without sticky sessions. Workers
|
||||
(`docker-compose.prod.yml`'s `worker: replicas: 2`) scale independently of
|
||||
the API — arq's Redis-backed queue ensures each job is claimed by exactly
|
||||
one worker.
|
||||
shared pool, so it's correct to run more than one API instance behind a
|
||||
reverse proxy without sticky sessions. Note that a bare `docker compose up`
|
||||
(as opposed to `docker stack deploy` under Swarm) does not itself honor a
|
||||
service's `deploy.replicas` — actually running multiple instances needs
|
||||
`docker compose -f docker-compose.prod.yml up -d --scale api=N`. Workers
|
||||
scale independently of the API the same way (`--scale worker=N`) — arq's
|
||||
Redis-backed queue ensures each job is claimed by exactly one worker.
|
||||
|
||||
Reference in New Issue
Block a user