Betrieb

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_PEPPER und DB_PASSWORD. Erzeuge sie mit openssl 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

  1. Öffne die Anmeldedaten-Seite der Google Cloud Console.
  2. Erstelle eine OAuth-2.0-Client-ID vom Typ Webanwendung.
  3. Füge unter Autorisierte JavaScript-Quellen die URL hinzu, die deine Nutzer öffnen (z. B. http://localhost:8080 oder https://routess.example.com).
  4. Füge unter Autorisierte Weiterleitungs-URIs <your-web-url>/auth/callback hinzu.
  5. Kopiere die Client-ID und das Client-Secret.

Ein Mapbox-Token besorgen

  1. Registriere dich unter account.mapbox.com.
  2. Erstelle ein öffentliches Access-Token (pk.…) unter Access tokens.
  3. 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:

ServiceImageStandard-Port
Postgrespostgres:18(intern)
APIghcr.io/robbeverhelst/routess-api3000
Webghcr.io/robbeverhelst/routess-web8080
Docsghcr.io/robbeverhelst/routess-docs3002

Ä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 -d

Oder 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:

VariableErforderlichStandardZweck
JWT_SECRETja-Signiert Session- und API-Tokens. Eine Rotation meldet alle ab.
ANALYTICS_SALTja-Hasht User-IDs in Analytics-Events. Eine Rotation setzt die Analytics-Pseudonyme zurück.
PAT_PEPPERja-Hasht Personal Access Tokens im Ruhezustand. Eine Rotation invalidiert jedes bestehende PAT.
DB_PASSWORDja-Postgres-Passwort.
GOOGLE_CLIENT_ID/SECRETja-Google-OAuth-Anmeldedaten.
VITE_MAPBOX_ACCESS_TOKENja-Mapbox-Access-Token, das von der Web-App genutzt wird.
PUBLIC_WEB_URLneinhttp://localhost:8080Wo Nutzer die App öffnen. Wird als OAuth-Redirect-Basis genutzt.
PUBLIC_API_URLneinhttp://localhost:3000Wo die Web-App die API erreicht.
ADMIN_EMAILSneinleerKommagetrennte E-Mails, denen bei der Anmeldung die Admin-Rolle gewährt wird. Leer deaktiviert das Admin-Panel.
RESEND_API_KEYneinleerResend-API-Key. Leer fällt darauf zurück, Verifizierungs-URLs auf stdout zu loggen.
EMAIL_FROMneinroutess <[email protected]>Absenderadresse, die Resend nutzt.
VALHALLA_URLjaleerBasis-URL einer selbst gehosteten Valhalla-Instanz für Routing und Belagsanalyse. Leer antworten die Routing-Endpunkte mit 503.
VITE_UMAMI_URL / _WEBSITE_IDneinleerUmami-Endpunkt und Site-ID für die Web-App. Leer deaktiviert die Produktanalyse.
UMAMI_URL / _WEBSITE_IDneinleerDerselbe Umami-Endpunkt und dieselbe Site-ID für die Docs-Seite (Next liest sie serverseitig).
VITE_SENTRY_DSNneinleerSentry-/GlitchTip-DSN für Browser-Fehlerberichte.
VITE_SENTRY_ENVIRONMENTneinproductionUmgebungs-Tag, das an Browser-Fehlerberichte gehängt wird.
PUBLIC_DOCS_URLneinhttp://localhost:3002Kanonischer 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):

VariableStandardZweck
LOG_LEVELinfoPino-Log-Level für die API (debug, info, warn, error).
SESSION_TTL_DAYS7Wie lange eine Browser-Session gültig bleibt.
SESSION_COOKIE_NAMEroutess_sessionName des HTTP-only-Session-Cookies.
JWT_EXPIRES_IN7dLebensdauer des JWT-Tokens.
FRONTEND_URLSleerKomma- oder zeilenumbruchgetrennte Liste erlaubter Web-Origins; überschreibt FRONTEND_URL, wenn gesetzt.
SWAGGER_ENABLEDfalse in prodStellt interaktive API-Docs unter SWAGGER_PATH bereit.
SWAGGER_PATH/apiPfad für die Swagger-UI, wenn aktiviert.
TELEMETRY_ENABLEDtrueHauptschalter für OpenTelemetry (Metriken + Traces).
METRICS_ENABLEDtruePrometheus-Metriken unter /metrics auf dem API-Port. Gibt 503 zurück, wenn deaktiviert.
OTEL_EXPORTER_OTLP_ENDPOINTleerOTLP-Collector-Basis-URL für Traces (/v1/traces wird angehängt). Leer deaktiviert den Trace-Export.
OTEL_EXPORTER_OTLP_HEADERSleerZusätzliche OTLP-Header als JSON-Objekt, z. B. {"authorization":"Bearer …"}.
GRAFANA_URLSleerJSON-Objekt mit Dashboard-Links, die im Admin-Panel angezeigt werden: {"apiOverview":"…","apiLatency":"…","runtime":"…"}.

Für den Web-Container:

VariableStandardZweck
VITE_SENTRY_TRACES_SAMPLE_RATE0.1Anteil der Browser-Sessions, die getrace werden, wenn Sentry aktiv ist.
VITE_SENTRY_DEBUGfalseAusfü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.

VariableStandardZweck
BILLING_ENABLEDfalseSchaltet den Pro-Pass, die Paywall und das Generierungskontingent pro Stufe ein.
BILLING_PROVIDERstripeZahlungsanbieter. Nur stripe ist implementiert.
BILLING_LAUNCHED_ATleerISO-Zeitstempel des Starts. Konten, die davor angelegt wurden, bekommen beim nächsten Login 3 Monate Pro.
STRIPE_SECRET_KEYleerGeheimer Stripe-Schlüssel (sk_live_… oder ein eingeschränkter rk_live_…).
STRIPE_WEBHOOK_SECRETleerSignaturgeheimnis (whsec_…) des Webhook-Endpunkts <api>/api/v1/billing/webhook.
STRIPE_PRICE_PRO_YEAR_PASSleerStripe-Price-ID des einmaligen Pro-Jahrespasses.
GENERATION_QUOTA_PER_DAY_ANONYMOUS1Generierte Routen pro Tag für nicht angemeldete Besucher (pro IP), solange die Bezahlung an ist. 0 hebt die Grenze auf.
GENERATION_QUOTA_PER_DAY_FREE3Dasselbe für angemeldete Nutzer im kostenlosen Plan.
GENERATION_QUOTA_PER_DAY_PRO50Dasselbe 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.com

PUBLIC_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 -d

Die 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).sql

Stelle 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.sql

Fehlerbehebung

  • Ist die API gesund? curl http://localhost:3000/health/ready gibt 200 zurück, wenn die API die Datenbank erreicht; /health/live prüft nur, ob der Prozess läuft.
  • "address already in use" auf :8080 oder :3000: ändere WEB_PORT / API_PORT in .env.
  • Google-Anmeldung gibt redirect_uri_mismatch zurück: die autorisierte Weiterleitungs-URI des OAuth-Clients muss exakt <PUBLIC_WEB_URL>/auth/callback entsprechen.
  • Kartenkacheln laden nicht: prüfe, ob VITE_MAPBOX_ACCESS_TOKEN gesetzt ist; suche in den Browser-Devtools nach 401ern von api.mapbox.com.
  • Verifizierungs-E-Mails kommen nicht an: setze RESEND_API_KEY und EMAIL_FROM. Ohne sie loggt die API die Verifizierungs-URL auf stdout; führe docker compose logs api | grep '\[email:console\]' aus.

On this page