From 9997e28967732e8db46db84a7bb471bd1c5a9f8e Mon Sep 17 00:00:00 2001 From: DeepZone Date: Wed, 20 May 2026 16:25:57 +0100 Subject: [PATCH] Make frontend API routing selfhost-friendly via nginx proxy --- .env.example | 8 ++-- README.md | 12 +++++- docker-compose.prod.yml | 2 - docker-compose.yml | 7 ++-- docs/operations/reverse-proxy.md | 64 ++++++++++++++++++++++++-------- frontend/Dockerfile | 3 +- frontend/nginx.conf | 29 +++++++++++++++ 7 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 frontend/nginx.conf diff --git a/.env.example b/.env.example index 22d7a10..ce5b74e 100644 --- a/.env.example +++ b/.env.example @@ -8,11 +8,11 @@ POSTGRES_USER=routeforge POSTGRES_PASSWORD=change-me DATABASE_URL=postgresql+psycopg://routeforge:change-me@postgres:5432/routeforge -# CORS (comma-separated list) -CORS_ORIGINS=http://localhost:3000 +# CORS (comma-separated list; mainly needed for split frontend/backend deployments) +CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000 -# Frontend runtime/build API target -VITE_API_URL=http://localhost:8000 +# Optional. Leave empty for same-origin /api proxy via frontend nginx. +VITE_API_URL= # RIPEstat RIPESTAT_BASE_URL=https://stat.ripe.net/data diff --git a/README.md b/README.md index 8fec9f4..67e54aa 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,8 @@ URLs: - Frontend: http://localhost:3000 - Backend: http://localhost:8000 - Health: http://localhost:8000/health -- System Info: http://localhost:8000/api/system/info +- System Info (via frontend proxy): http://localhost:3000/api/system/info +- Direct System Info (backend): http://localhost:8000/api/system/info ## Demo Mode @@ -157,10 +158,17 @@ docker compose up --build ### Production Start ```bash cp .env.example .env -# edit .env (especially POSTGRES_PASSWORD, DATABASE_URL, CORS_ORIGINS, VITE_API_URL) +# edit .env (especially POSTGRES_PASSWORD, DATABASE_URL, CORS_ORIGINS) docker compose -f docker-compose.prod.yml up -d --build ``` +### Selfhosting Networking Default +- Frontend UI: `http://:3000` +- API via same host (proxied by frontend nginx): `http://:3000/api/...` +- Direct backend access (optional/diagnostics): `http://:8000` + +In the standard setup, RouteForge does **not** require a hardcoded host IP in the frontend build. The frontend nginx proxies `/api` internally to the backend service (`backend:8000`). + ### Environment - `.env.example` documents required production variables. - Keep RouteForge read-only (no write operations to RIPE DB, RPKI, or routers). diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 3f6a9ee..b524f2f 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -40,8 +40,6 @@ services: frontend: build: context: ./frontend - args: - VITE_API_URL: ${VITE_API_URL} restart: unless-stopped depends_on: backend: diff --git a/docker-compose.yml b/docker-compose.yml index 00261fe..58f1d5e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,12 +6,11 @@ services: - routeforge_data:/app/data environment: - DATABASE_URL=sqlite:////app/data/routeforge.db - - CORS_ORIGINS=http://192.168.58.167:3000,http://127.0.0.1:3000 + - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000} frontend: - build: ./frontend + build: + context: ./frontend ports: ["3000:80"] - environment: - - VITE_API_URL=http://192.168.58.167:8000 depends_on: [backend] volumes: routeforge_data: diff --git a/docs/operations/reverse-proxy.md b/docs/operations/reverse-proxy.md index 7dcd479..c32d056 100644 --- a/docs/operations/reverse-proxy.md +++ b/docs/operations/reverse-proxy.md @@ -1,16 +1,49 @@ # Reverse Proxy -## Recommended patterns -1. **Simple port exposure**: publish `3000` (frontend) and optionally `8000` (backend) on trusted networks. -2. **Domain + reverse proxy**: expose only proxy, keep backend internal where possible. +## Empfehlung +Für einfaches Selfhosting wird **Variante A** empfohlen. Dabei zeigt Ihr externer Reverse Proxy nur auf den Frontend-Service. API-Aufrufe unter `/api` funktionieren automatisch über den internen Frontend-Nginx-Proxy. -## Nginx Proxy Manager example -- Proxy Host domain `routeforge.example.com` -> `frontend:80`. -- Add advanced location `/api` -> `backend:8000`. -- Optional `/health` -> `backend:8000/health`. -- Enable HTTPS certificate (Let's Encrypt recommended). +## Variante A: Externer Proxy -> `frontend:80` (empfohlen) +- Externer Reverse Proxy leitet `/` an `frontend:80` weiter. +- Frontend-Nginx liefert SPA-Dateien aus. +- Frontend-Nginx proxied `/api/` intern an `backend:8000/api/`. +- Frontend-Nginx proxied `/health` intern an `backend:8000/health`. -## Classic Nginx example +Vorteile: +- Einfachste Konfiguration +- Kein separates externes `/api`-Routing nötig +- Keine hardcodierte Host-IP im Frontend-Build erforderlich + +## Variante B: Externer Proxy split-routed `/` und `/api` +- Externer Reverse Proxy leitet `/` an `frontend:80` weiter. +- Externer Reverse Proxy leitet `/api/` direkt an `backend:8000/api/` weiter. +- Optional `/health` direkt an `backend:8000/health`. + +Diese Variante ist nur für spezielle Setups nötig (z. B. wenn API-Traffic getrennt behandelt werden soll). + +## Nginx Proxy Manager Beispiel +- Proxy Host Domain `routeforge.example.com` -> `frontend:80`. +- Bei Variante B zusätzliche Advanced Locations für `/api` und optional `/health` auf `backend:8000`. +- HTTPS-Zertifikat aktivieren (Let's Encrypt empfohlen). + +## Classic Nginx Beispiele + +### Variante A (empfohlen) +```nginx +server { + listen 443 ssl; + server_name routeforge.example.com; + + location / { + proxy_pass http://frontend:80; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +### Variante B (nur bei Bedarf) ```nginx server { listen 443 ssl; @@ -32,10 +65,11 @@ server { } ``` -## HTTPS and CORS -- Prefer HTTPS termination at proxy. -- Set `CORS_ORIGINS` to the public frontend origin(s). -- RouteForge currently does not require websocket proxy settings. +## HTTPS und CORS +- HTTPS-Termination bevorzugt im externen Proxy. +- Bei Standard-Setup mit Variante A sind Browser-API-Calls same-origin (`/api` über denselben Host). +- `CORS_ORIGINS` ist primär relevant für getrennte Frontend/Backend-Deployments. -## Security note -- Avoid exposing backend directly to the internet if frontend is already proxied. +## Security-Hinweis +- Backend-Port `8000` nur exponieren, wenn benötigt (z. B. Diagnose). +- Wenn Frontend bereits sauber über Proxy bereitgestellt wird, Backend nach Möglichkeit intern halten. diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 15d3742..2d99691 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -3,11 +3,12 @@ WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . -ARG VITE_API_URL=http://localhost:8000 +ARG VITE_API_URL= ENV VITE_API_URL=${VITE_API_URL} RUN npm run build FROM nginx:1.27-alpine COPY --from=build /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..3682c14 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,29 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location /api/ { + proxy_pass http://backend:8000/api/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /health { + proxy_pass http://backend:8000/health; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location / { + try_files $uri $uri/ /index.html; + } +}