Self-Hosting
Betreibe deine eigene routess-Instanz mit Docker Compose.
Diese Anleitung bringt eine routess-Instanz in etwa fünf Minuten auf deiner eigenen Maschine oder deinem Server zum Laufen. Für Kubernetes siehe Kubernetes.
Was du brauchst
- Docker und Docker Compose v2 (modernes Docker Desktop oder Docker Engine bringt beides mit).
- Einen Google-OAuth-Client für die Google-Anmeldung (das Compose-File benötigt ihn). Nutzer können sich außerdem mit E-Mail + Passwort anmelden, sobald der Stack läuft.
- Ein Mapbox-Access-Token; die kostenlose Stufe reicht.
- Eine Valhalla-Routing-Engine (
VALHALLA_URL). Routenberechnung und Belagsanalyse laufen darüber; ohne sie antworten die Routing-Endpunkte mit 503. - Lange zufällige Strings für
JWT_SECRET,ANALYTICS_SALT,PAT_PEPPERundDB_PASSWORD. Erzeuge sie mitopenssl rand -base64 32.
Optional, sauber deaktiviert, wenn nicht gesetzt:
- Resend für transaktionale E-Mails (ohne werden Verifizierungs-URLs auf stdout geloggt).
- Sentry / GlitchTip für Browser-Fehlerberichte.
- Umami für Produktanalysen.
- Admin-Rolle für den Zugriff auf das In-App-Admin-Panel.
Google OAuth einrichten
- Öffne die Anmeldedaten-Seite der Google Cloud Console.
- Erstelle eine OAuth-2.0-Client-ID vom Typ Webanwendung.
- Füge unter Autorisierte JavaScript-Quellen die URL hinzu, die deine Nutzer öffnen (z. B.
http://localhost:8080oderhttps://routess.example.com). - Füge unter Autorisierte Weiterleitungs-URIs
<your-web-url>/auth/callbackhinzu. - Kopiere die Client-ID und das Client-Secret.
Ein Mapbox-Token besorgen
- Registriere dich unter account.mapbox.com.
- Erstelle ein öffentliches Access-Token (
pk.…) unter Access tokens. - Beschränke es auf deine Domain, wenn du in Produktion gehst.
Ausführen
git clone https://github.com/robbeverhelst/routess.git
cd routess
cp docker/.env.selfhost.example .env
# edit .env and fill in the required values
docker compose -f docker/compose.selfhost.yaml --project-directory . up -dÖffne dann http://localhost:8080.
Der Stack startet vier Container:
| Service | Image | Standard-Port |
|---|---|---|
| Postgres | postgres:18 | (intern) |
| API | ghcr.io/robbeverhelst/routess-api | 3000 |
| Web | ghcr.io/robbeverhelst/routess-web | 8080 |
| Docs | ghcr.io/robbeverhelst/routess-docs | 3002 |
Ändere die Host-Ports über WEB_PORT, API_PORT, DOCS_PORT in .env.
Eine Version pinnen
Der Standard-Tag ist latest. Für Produktion pinne auf einen bestimmten Release:
ROUTESS_TAG=1.122.7 docker compose -f docker/compose.selfhost.yaml --project-directory . up -dOder setze ROUTESS_TAG in .env. Verfügbare Tags findest du auf der Releases-Seite.
Umgebungsvariablen
Die .env-Datei in docker/.env.selfhost.example ist die maßgebliche Quelle. Die Variablen, die du am häufigsten anfasst:
| Variable | Erforderlich | Standard | Zweck |
|---|---|---|---|
JWT_SECRET | ja | - | Signiert Session- und API-Tokens. Eine Rotation meldet alle ab. |
ANALYTICS_SALT | ja | - | Hasht User-IDs in Analytics-Events. Eine Rotation setzt die Analytics-Pseudonyme zurück. |
PAT_PEPPER | ja | - | Hasht Personal Access Tokens im Ruhezustand. Eine Rotation invalidiert jedes bestehende PAT. |
DB_PASSWORD | ja | - | Postgres-Passwort. |
GOOGLE_CLIENT_ID/SECRET | ja | - | Google-OAuth-Anmeldedaten. |
VITE_MAPBOX_ACCESS_TOKEN | ja | - | Mapbox-Access-Token, das von der Web-App genutzt wird. |
PUBLIC_WEB_URL | nein | http://localhost:8080 | Wo Nutzer die App öffnen. Wird als OAuth-Redirect-Basis genutzt. |
PUBLIC_API_URL | nein | http://localhost:3000 | Wo die Web-App die API erreicht. |
ADMIN_EMAILS | nein | leer | Kommagetrennte E-Mails, denen bei der Anmeldung die Admin-Rolle gewährt wird. Leer deaktiviert das Admin-Panel. |
RESEND_API_KEY | nein | leer | Resend-API-Key. Leer fällt darauf zurück, Verifizierungs-URLs auf stdout zu loggen. |
EMAIL_FROM | nein | routess <[email protected]> | Absenderadresse, die Resend nutzt. |
VALHALLA_URL | ja | leer | Basis-URL einer selbst gehosteten Valhalla-Instanz für Routing und Belagsanalyse. Leer antworten die Routing-Endpunkte mit 503. |
VITE_UMAMI_URL / _WEBSITE_ID | nein | leer | Umami-Endpunkt und Site-ID für die Web-App. Leer deaktiviert die Produktanalyse. |
UMAMI_URL / _WEBSITE_ID | nein | leer | Derselbe Umami-Endpunkt und dieselbe Site-ID für die Docs-Seite (Next liest sie serverseitig). |
VITE_SENTRY_DSN | nein | leer | Sentry-/GlitchTip-DSN für Browser-Fehlerberichte. |
VITE_SENTRY_ENVIRONMENT | nein | production | Umgebungs-Tag, das an Browser-Fehlerberichte gehängt wird. |
PUBLIC_DOCS_URL | nein | http://localhost:3002 | Kanonischer Origin der Docs-Seite, genutzt in ihren sitemap-, robots.txt- und llms.txt-Links. |
Erweiterte Variablen
Die API versteht mehr Stellschrauben, als das Compose-File durchreicht. Um eine zu nutzen, füge sie unter dem environment des api-Service in docker/compose.selfhost.yaml hinzu (oder in deiner eigenen Override-Datei):
| Variable | Standard | Zweck |
|---|---|---|
LOG_LEVEL | info | Pino-Log-Level für die API (debug, info, warn, error). |
SESSION_TTL_DAYS | 7 | Wie lange eine Browser-Session gültig bleibt. |
SESSION_COOKIE_NAME | routess_session | Name des HTTP-only-Session-Cookies. |
JWT_EXPIRES_IN | 7d | Lebensdauer des JWT-Tokens. |
FRONTEND_URLS | leer | Komma- oder zeilenumbruchgetrennte Liste erlaubter Web-Origins; überschreibt FRONTEND_URL, wenn gesetzt. |
SWAGGER_ENABLED | false in prod | Stellt interaktive API-Docs unter SWAGGER_PATH bereit. |
SWAGGER_PATH | /api | Pfad für die Swagger-UI, wenn aktiviert. |
TELEMETRY_ENABLED | true | Hauptschalter für OpenTelemetry (Metriken + Traces). |
METRICS_ENABLED | true | Prometheus-Metriken unter /metrics auf dem API-Port. Gibt 503 zurück, wenn deaktiviert. |
OTEL_EXPORTER_OTLP_ENDPOINT | leer | OTLP-Collector-Basis-URL für Traces (/v1/traces wird angehängt). Leer deaktiviert den Trace-Export. |
OTEL_EXPORTER_OTLP_HEADERS | leer | Zusätzliche OTLP-Header als JSON-Objekt, z. B. {"authorization":"Bearer …"}. |
GRAFANA_URLS | leer | JSON-Objekt mit Dashboard-Links, die im Admin-Panel angezeigt werden: {"apiOverview":"…","apiLatency":"…","runtime":"…"}. |
Für den Web-Container:
| Variable | Standard | Zweck |
|---|---|---|
VITE_SENTRY_TRACES_SAMPLE_RATE | 0.1 | Anteil der Browser-Sessions, die getrace werden, wenn Sentry aktiv ist. |
VITE_SENTRY_DEBUG | false | Ausführliches Logging des Sentry-SDK. |
Das Browser-Konsolen-Logging steht standardmäßig auf warn. Um einen einzelnen Browser zu debuggen, setze localStorage["routess:log-level"] auf debug, info, warn, error oder none und lade neu.
Bezahlung (nur das gehostete Produkt)
Routess.com verkauft einen Pro-Jahrespass (ADR 0039). Eine selbst gehostete Instanz lässt die Bezahlung aus, das ist der Standard: Jede Funktion bleibt freigeschaltet, alle bekommen GENERATION_QUOTA_PER_DAY generierte Routen pro Tag, und kein Zahlungsanbieter wird kontaktiert. Lass BILLING_ENABLED=false stehen oder setze es nicht.
Zur Referenz: Diese Variablen setzt das gehostete Produkt. Mit BILLING_ENABLED=true startet die API nur, wenn der Startzeitpunkt und alle drei Stripe-Werte gesetzt sind.
| Variable | Standard | Zweck |
|---|---|---|
BILLING_ENABLED | false | Schaltet den Pro-Pass, die Paywall und das Generierungskontingent pro Stufe ein. |
BILLING_PROVIDER | stripe | Zahlungsanbieter. Nur stripe ist implementiert. |
BILLING_LAUNCHED_AT | leer | ISO-Zeitstempel des Starts. Konten, die davor angelegt wurden, bekommen beim nächsten Login 3 Monate Pro. |
STRIPE_SECRET_KEY | leer | Geheimer Stripe-Schlüssel (sk_live_… oder ein eingeschränkter rk_live_…). |
STRIPE_WEBHOOK_SECRET | leer | Signaturgeheimnis (whsec_…) des Webhook-Endpunkts <api>/api/v1/billing/webhook. |
STRIPE_PRICE_PRO_YEAR_PASS | leer | Stripe-Price-ID des einmaligen Pro-Jahrespasses. |
GENERATION_QUOTA_PER_DAY_ANONYMOUS | 1 | Generierte Routen pro Tag für nicht angemeldete Besucher (pro IP), solange die Bezahlung an ist. 0 hebt die Grenze auf. |
GENERATION_QUOTA_PER_DAY_FREE | 3 | Dasselbe für angemeldete Nutzer im kostenlosen Plan. |
GENERATION_QUOTA_PER_DAY_PRO | 50 | Dasselbe für Pro-Nutzer. |
Hinter einen Reverse-Proxy stellen
Das Standard-Compose exponiert die Ports direkt. Für TLS und eine echte Domain stelle den web- und api-Services einen Reverse-Proxy voran (Caddy, Traefik, ingress-nginx, Cloudflare Tunnel) und überschreibe die öffentlichen URLs in .env:
PUBLIC_WEB_URL=https://routess.example.com
PUBLIC_API_URL=https://api.routess.example.comPUBLIC_WEB_URL wird auch als OAuth-Redirect-Basis genutzt und muss daher exakt mit dem übereinstimmen, was du in der Google Cloud konfiguriert hast.
Upgraden
Erstelle zuerst ein Datenbank-Backup (siehe unten); Migrationen sind nur vorwärts gerichtet, sodass ein Snapshot dein Rollback-Pfad ist. Dann:
docker compose -f docker/compose.selfhost.yaml --project-directory . pull
docker compose -f docker/compose.selfhost.yaml --project-directory . up -dDie API führt Migrationen beim Start automatisch aus. Es gibt keinen manuellen Migrationsschritt. Zum Zurückrollen stelle den Dump von vor dem Upgrade wieder her und pinne ROUTESS_TAG auf den vorherigen Release.
Sichern
Die Postgres-Daten liegen im benannten Volume postgres_data. Standard-pg_dump funktioniert:
docker compose -f docker/compose.selfhost.yaml --project-directory . exec postgres \
pg_dump -U routess routess > routess-$(date +%F).sqlStelle mit psql in ein frisches Volume wieder her:
docker compose -f docker/compose.selfhost.yaml --project-directory . exec -T postgres \
psql -U routess routess < routess-2026-06-04.sqlFehlerbehebung
- Ist die API gesund?
curl http://localhost:3000/health/readygibt 200 zurück, wenn die API die Datenbank erreicht;/health/liveprüft nur, ob der Prozess läuft. - "address already in use" auf
:8080oder:3000: ändereWEB_PORT/API_PORTin.env. - Google-Anmeldung gibt
redirect_uri_mismatchzurück: die autorisierte Weiterleitungs-URI des OAuth-Clients muss exakt<PUBLIC_WEB_URL>/auth/callbackentsprechen. - Kartenkacheln laden nicht: prüfe, ob
VITE_MAPBOX_ACCESS_TOKENgesetzt ist; suche in den Browser-Devtools nach 401ern vonapi.mapbox.com. - Verifizierungs-E-Mails kommen nicht an: setze
RESEND_API_KEYundEMAIL_FROM. Ohne sie loggt die API die Verifizierungs-URL auf stdout; führedocker compose logs api | grep '\[email:console\]'aus.