docs: add server deployment guide and compose env wiring

This commit is contained in:
yves.gugger
2025-12-12 12:22:29 +01:00
parent 83ea218190
commit 496b0ff628
7 changed files with 280 additions and 19 deletions

View File

@ -0,0 +1,46 @@
# Docker Compose environment (NO SECRETS)
#
# Copy to `.env` (it is gitignored):
# cp DEPLOY_docker_compose.env.example .env
#
# Then set real values (password manager / vault).
# Core (required)
DB_PASSWORD=change-me
SECRET_KEY=GENERATE_A_LONG_RANDOM_SECRET
ENVIRONMENT=production
SITE_URL=https://your-domain.com
# CORS (only needed if frontend and backend are different origins)
ALLOWED_ORIGINS=https://your-domain.com,https://www.your-domain.com
# Cookies (optional)
COOKIE_SECURE=true
# COOKIE_DOMAIN=.your-domain.com
# Email (optional but recommended for alerts)
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# SMTP_USER=
# SMTP_PASSWORD=
# SMTP_FROM_EMAIL=
# SMTP_FROM_NAME=pounce
# SMTP_USE_TLS=true
# SMTP_USE_SSL=false
# CONTACT_EMAIL=
# OAuth (optional)
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
# GOOGLE_REDIRECT_URI=https://your-domain.com/api/v1/oauth/google/callback
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=
# GITHUB_REDIRECT_URI=https://your-domain.com/api/v1/oauth/github/callback
# Stripe (optional)
# STRIPE_SECRET_KEY=
# STRIPE_WEBHOOK_SECRET=
# STRIPE_PRICE_TRADER=
# STRIPE_PRICE_TYCOON=

View File

@ -3,5 +3,5 @@
# Copy to a *local-only* file and keep it OUT of git:
# cp DEPLOY_frontend.env.example DEPLOY_frontend.env
#
NEXT_PUBLIC_API_URL=https://your-domain.com/api
NEXT_PUBLIC_API_URL=https://your-domain.com/api/v1

View File

@ -54,6 +54,10 @@ For the new cookie-auth to “just work”, the recommended setup is:
- **Serve the frontend on your main domain**
- **Route `/api/v1/*` to the backend via reverse proxy** (nginx/caddy/Next rewrite)
## Server deployment (recommended)
See `SERVER_DEPLOYMENT.md`.
### Env files (important)
- **Never commit** any of these:

170
SERVER_DEPLOYMENT.md Normal file
View File

@ -0,0 +1,170 @@
# Server Deployment (Docker Compose)
## Ziel
Pounce auf einem Server starten mit:
- **Frontend** (Next.js)
- **Backend API** (FastAPI)
- **Postgres**
- **Redis** (Rate-Limit Storage + Job Queue)
- **Scheduler** (APScheduler) **separater Prozess**
- **Worker** (ARQ) **separater Prozess**
Damit laufen Jobs nicht mehrfach bei mehreren API-Workern und die UI bleibt schnell.
---
## Voraussetzungen
- Linux Server (z.B. Ubuntu 22.04+)
- Docker + Docker Compose Plugin
- Domain + HTTPS Reverse Proxy (empfohlen), damit Cookie-Auth zuverlässig funktioniert
---
## 1) Repo auf den Server holen
```bash
cd /opt
git clone <your-repo-url> pounce
cd pounce
```
---
## 2) Server-Environment anlegen
In `/opt/pounce`:
```bash
cp DEPLOY_docker_compose.env.example .env
```
Dann `.env` öffnen und mindestens setzen:
- **DB_PASSWORD**
- **SECRET_KEY**
- **SITE_URL** (z.B. `https://pounce.example.com`)
- **ALLOWED_ORIGINS** (z.B. `https://pounce.example.com`)
Optional (aber empfohlen):
- **SMTP_\*** (für Alerts/Emails)
- **COOKIE_DOMAIN** (wenn du Cookies über Subdomains teilen willst)
---
## 3) Starten
```bash
docker compose up -d --build
```
Services:
- `frontend` (Port 3000)
- `backend` (Port 8000)
- `scheduler` (kein Port)
- `worker` (kein Port)
- `db` (kein Port)
- `redis` (kein Port)
---
## 4) Initial Setup (1× nach erstem Start)
### DB Tabellen + Baseline Seed
```bash
docker compose exec backend python scripts/init_db.py
```
### TLD Price Seed (886+)
```bash
docker compose exec backend python scripts/seed_tld_prices.py
```
---
## 5) Reverse Proxy (empfohlen)
### Warum?
Das Frontend ruft im Browser standardmässig `https://<domain>/api/v1/...` auf (same-origin).
Darum solltest du:
- **HTTPS** terminieren
- `/api/v1/*` an das Backend routen
- `/` an das Frontend routen
### Beispiel: Caddy (sehr simpel)
```caddy
pounce.example.com {
encode zstd gzip
# API
handle_path /api/v1/* {
reverse_proxy 127.0.0.1:8000
}
# Frontend
reverse_proxy 127.0.0.1:3000
# optional: metrics nur intern
@metrics path /metrics
handle @metrics {
respond 403
}
}
```
Wichtig:
- Setze `SITE_URL=https://pounce.example.com`
- Setze `COOKIE_SECURE=true` (oder via `ENVIRONMENT=production`)
---
## 6) Checks (nach Deploy)
```bash
curl -f http://127.0.0.1:8000/health
curl -f http://127.0.0.1:8000/metrics
```
Logs:
```bash
docker compose logs -f backend
docker compose logs -f scheduler
docker compose logs -f worker
```
---
## 7) Updates
```bash
cd /opt/pounce
git pull
docker compose up -d --build
```
---
## Troubleshooting (häufig)
- **Cookies/Login klappt nicht**:
- Prüfe `SITE_URL` und HTTPS (Secure Cookies)
- Prüfe `ALLOWED_ORIGINS` (falls Frontend/Backend nicht same-origin sind)
- **Scheduler läuft doppelt**:
- Stelle sicher, dass nur **ein** `scheduler` Service läuft (keine zweite Instanz)
- **Emails werden nicht gesendet**:
- `docker compose exec scheduler env | grep SMTP_`
- SMTP Vars müssen im Container vorhanden sein (kommen aus `.env`)

View File

@ -118,14 +118,15 @@ async def rate_limit_handler(request: Request, exc: RateLimitExceeded):
},
)
# Get allowed origins from environment
ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "").split(",")
if not ALLOWED_ORIGINS or ALLOWED_ORIGINS == [""]:
ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://10.42.0.73:3000",
]
# Get allowed origins (env overrides settings)
origins_raw = (
os.getenv("ALLOWED_ORIGINS", "").strip()
or os.getenv("CORS_ORIGINS", "").strip()
or (settings.cors_origins or "").strip()
)
ALLOWED_ORIGINS = [o.strip() for o in origins_raw.split(",") if o.strip()]
if not ALLOWED_ORIGINS:
ALLOWED_ORIGINS = ["http://localhost:3000", "http://127.0.0.1:3000"]
# Add production origins
SITE_URL = os.getenv("SITE_URL", "")

View File

@ -1,5 +1,45 @@
version: '3.8'
x-backend-env: &backend-env
DATABASE_URL: postgresql+asyncpg://pounce:${DB_PASSWORD:-changeme}@db:5432/pounce
SECRET_KEY: ${SECRET_KEY:-change-this-in-production}
ENVIRONMENT: ${ENVIRONMENT:-production}
SITE_URL: ${SITE_URL:-}
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-}
COOKIE_DOMAIN: ${COOKIE_DOMAIN:-}
COOKIE_SECURE: ${COOKIE_SECURE:-true}
# Optional: SMTP (email alerts)
SMTP_HOST: ${SMTP_HOST:-}
SMTP_PORT: ${SMTP_PORT:-587}
SMTP_USER: ${SMTP_USER:-}
SMTP_PASSWORD: ${SMTP_PASSWORD:-}
SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-}
SMTP_FROM_NAME: ${SMTP_FROM_NAME:-pounce}
SMTP_USE_TLS: ${SMTP_USE_TLS:-true}
SMTP_USE_SSL: ${SMTP_USE_SSL:-false}
CONTACT_EMAIL: ${CONTACT_EMAIL:-}
# Optional: OAuth
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-}
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-}
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID:-}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET:-}
GITHUB_REDIRECT_URI: ${GITHUB_REDIRECT_URI:-}
# Optional: Stripe
STRIPE_SECRET_KEY: ${STRIPE_SECRET_KEY:-}
STRIPE_WEBHOOK_SECRET: ${STRIPE_WEBHOOK_SECRET:-}
STRIPE_PRICE_TRADER: ${STRIPE_PRICE_TRADER:-}
STRIPE_PRICE_TYCOON: ${STRIPE_PRICE_TYCOON:-}
# Optional integrations
DROPCATCH_CLIENT_ID: ${DROPCATCH_CLIENT_ID:-}
DROPCATCH_CLIENT_SECRET: ${DROPCATCH_CLIENT_SECRET:-}
DROPCATCH_API_BASE: ${DROPCATCH_API_BASE:-https://api.dropcatch.com}
SEDO_PARTNER_ID: ${SEDO_PARTNER_ID:-}
SEDO_SIGN_KEY: ${SEDO_SIGN_KEY:-}
SEDO_API_BASE: ${SEDO_API_BASE:-https://api.sedo.com/api/v1/}
MOZ_ACCESS_ID: ${MOZ_ACCESS_ID:-}
MOZ_SECRET_KEY: ${MOZ_SECRET_KEY:-}
services:
# PostgreSQL Database
db:
@ -42,9 +82,7 @@ services:
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql+asyncpg://pounce:${DB_PASSWORD:-changeme}@db:5432/pounce
SECRET_KEY: ${SECRET_KEY:-change-this-in-production}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
<<: *backend-env
ENABLE_SCHEDULER: "false"
ENABLE_JOB_QUEUE: "true"
REDIS_URL: redis://redis:6379/0
@ -69,8 +107,7 @@ services:
container_name: pounce-scheduler
restart: unless-stopped
environment:
DATABASE_URL: postgresql+asyncpg://pounce:${DB_PASSWORD:-changeme}@db:5432/pounce
SECRET_KEY: ${SECRET_KEY:-change-this-in-production}
<<: *backend-env
ENABLE_SCHEDULER: "true"
depends_on:
db:
@ -85,8 +122,7 @@ services:
container_name: pounce-worker
restart: unless-stopped
environment:
DATABASE_URL: postgresql+asyncpg://pounce:${DB_PASSWORD:-changeme}@db:5432/pounce
SECRET_KEY: ${SECRET_KEY:-change-this-in-production}
<<: *backend-env
ENABLE_SCHEDULER: "false"
ENABLE_JOB_QUEUE: "true"
REDIS_URL: redis://redis:6379/0
@ -107,7 +143,8 @@ services:
ports:
- "3000:3000"
environment:
NEXT_PUBLIC_API_URL: ${API_URL:-http://localhost:8000}
# Internal backend URL for server-side requests
BACKEND_URL: http://backend:8000
depends_on:
- backend

View File

@ -10,8 +10,11 @@
const getApiBase = (): string => {
// Server-side rendering: use environment variable or localhost
if (typeof window === 'undefined') {
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1'
return baseUrl.replace(/\/$/, '')
// Prefer internal backend URL (server-only) when running in Docker/SSR
const backendUrl = process.env.BACKEND_URL?.replace(/\/$/, '')
const configured = (backendUrl ? `${backendUrl}/api/v1` : process.env.NEXT_PUBLIC_API_URL) || 'http://localhost:8000/api/v1'
const normalized = configured.replace(/\/$/, '')
return normalized.endsWith('/api/v1') ? normalized : `${normalized}/api/v1`
}
const { protocol, hostname, port } = window.location