docs: add server deployment guide and compose env wiring
This commit is contained in:
46
DEPLOY_docker_compose.env.example
Normal file
46
DEPLOY_docker_compose.env.example
Normal 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=
|
||||||
|
|
||||||
|
|
||||||
@ -3,5 +3,5 @@
|
|||||||
# Copy to a *local-only* file and keep it OUT of git:
|
# Copy to a *local-only* file and keep it OUT of git:
|
||||||
# cp DEPLOY_frontend.env.example DEPLOY_frontend.env
|
# 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
|
||||||
|
|
||||||
|
|||||||
@ -54,6 +54,10 @@ For the new cookie-auth to “just work”, the recommended setup is:
|
|||||||
- **Serve the frontend on your main domain**
|
- **Serve the frontend on your main domain**
|
||||||
- **Route `/api/v1/*` to the backend via reverse proxy** (nginx/caddy/Next rewrite)
|
- **Route `/api/v1/*` to the backend via reverse proxy** (nginx/caddy/Next rewrite)
|
||||||
|
|
||||||
|
## Server deployment (recommended)
|
||||||
|
|
||||||
|
See `SERVER_DEPLOYMENT.md`.
|
||||||
|
|
||||||
### Env files (important)
|
### Env files (important)
|
||||||
|
|
||||||
- **Never commit** any of these:
|
- **Never commit** any of these:
|
||||||
|
|||||||
170
SERVER_DEPLOYMENT.md
Normal file
170
SERVER_DEPLOYMENT.md
Normal 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`)
|
||||||
|
|
||||||
|
|
||||||
@ -118,14 +118,15 @@ async def rate_limit_handler(request: Request, exc: RateLimitExceeded):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# Get allowed origins from environment
|
# Get allowed origins (env overrides settings)
|
||||||
ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "").split(",")
|
origins_raw = (
|
||||||
if not ALLOWED_ORIGINS or ALLOWED_ORIGINS == [""]:
|
os.getenv("ALLOWED_ORIGINS", "").strip()
|
||||||
ALLOWED_ORIGINS = [
|
or os.getenv("CORS_ORIGINS", "").strip()
|
||||||
"http://localhost:3000",
|
or (settings.cors_origins or "").strip()
|
||||||
"http://127.0.0.1:3000",
|
)
|
||||||
"http://10.42.0.73:3000",
|
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
|
# Add production origins
|
||||||
SITE_URL = os.getenv("SITE_URL", "")
|
SITE_URL = os.getenv("SITE_URL", "")
|
||||||
|
|||||||
@ -1,5 +1,45 @@
|
|||||||
version: '3.8'
|
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:
|
services:
|
||||||
# PostgreSQL Database
|
# PostgreSQL Database
|
||||||
db:
|
db:
|
||||||
@ -42,9 +82,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql+asyncpg://pounce:${DB_PASSWORD:-changeme}@db:5432/pounce
|
<<: *backend-env
|
||||||
SECRET_KEY: ${SECRET_KEY:-change-this-in-production}
|
|
||||||
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
|
|
||||||
ENABLE_SCHEDULER: "false"
|
ENABLE_SCHEDULER: "false"
|
||||||
ENABLE_JOB_QUEUE: "true"
|
ENABLE_JOB_QUEUE: "true"
|
||||||
REDIS_URL: redis://redis:6379/0
|
REDIS_URL: redis://redis:6379/0
|
||||||
@ -69,8 +107,7 @@ services:
|
|||||||
container_name: pounce-scheduler
|
container_name: pounce-scheduler
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql+asyncpg://pounce:${DB_PASSWORD:-changeme}@db:5432/pounce
|
<<: *backend-env
|
||||||
SECRET_KEY: ${SECRET_KEY:-change-this-in-production}
|
|
||||||
ENABLE_SCHEDULER: "true"
|
ENABLE_SCHEDULER: "true"
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
@ -85,8 +122,7 @@ services:
|
|||||||
container_name: pounce-worker
|
container_name: pounce-worker
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql+asyncpg://pounce:${DB_PASSWORD:-changeme}@db:5432/pounce
|
<<: *backend-env
|
||||||
SECRET_KEY: ${SECRET_KEY:-change-this-in-production}
|
|
||||||
ENABLE_SCHEDULER: "false"
|
ENABLE_SCHEDULER: "false"
|
||||||
ENABLE_JOB_QUEUE: "true"
|
ENABLE_JOB_QUEUE: "true"
|
||||||
REDIS_URL: redis://redis:6379/0
|
REDIS_URL: redis://redis:6379/0
|
||||||
@ -107,7 +143,8 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
environment:
|
environment:
|
||||||
NEXT_PUBLIC_API_URL: ${API_URL:-http://localhost:8000}
|
# Internal backend URL for server-side requests
|
||||||
|
BACKEND_URL: http://backend:8000
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
|
|||||||
@ -10,8 +10,11 @@
|
|||||||
const getApiBase = (): string => {
|
const getApiBase = (): string => {
|
||||||
// Server-side rendering: use environment variable or localhost
|
// Server-side rendering: use environment variable or localhost
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1'
|
// Prefer internal backend URL (server-only) when running in Docker/SSR
|
||||||
return baseUrl.replace(/\/$/, '')
|
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
|
const { protocol, hostname, port } = window.location
|
||||||
|
|||||||
Reference in New Issue
Block a user