AI-Gateway-Routing: n8n-Workflows über zentralen LiteLLM-Proxy

Wie ich alle n8n-Workflows und Blog-Agenten über einen zentralen LiteLLM-Proxy (ai.syfrt.de) routen ließ – mit Docker, Coolify und echten Fallstricken.

Dunkles Netzwerk-Diagramm: n8n-Workflows und Blog-Agent senden Anfragen über zentralen LiteLLM-Proxy (ai.syfrt.de) an OpenAI, mit Traefik v3.7 als Reverse Proxy und PostgreSQL-Logging im Hintergrund

Warum ein zentraler AI-Gateway für alle Workflows?

Vergangene Woche stand ein Refactoring an, das ich schon länger vor mir hergeschoben hatte: Alle n8n-Workflows und der wöchentliche Blog-Agent sollten nicht mehr direkt gegen die OpenAI-API laufen, sondern über einen zentralen LiteLLM-Proxy (ai.syfrt.de) geroutet werden.

Der Grund war simpel: Ich wollte ein einziges Logging-Backend, Caching für identische Prompts und Fallback-Logik (falls OpenAI mal hakt, könnte ich auf Anthropic oder lokale Modelle ausweichen) – ohne jeden einzelnen n8n-Node anfassen zu müssen.

LiteLLM ist ein schlanker Python-Proxy, der die OpenAI-API nachbildet und Anfragen transparent an verschiedene LLM-Provider weiterleitet. Ich hatte ihn bereits als Docker-Container auf meinem Hetzner CX33 laufen (siehe früherer Artikel), aber bisher nur für Experimente genutzt. Jetzt sollte er produktiv werden.

Architektur-Realität: Traefik, Docker und DNS

Mein Setup sieht so aus:

  • Coolify (PaaS) managt alle Docker-Container auf dem CX33.
  • Traefik v3.7 läuft als Reverse Proxy und terminiert TLS (Let’s Encrypt).
  • LiteLLM läuft als Docker-Container mit PostgreSQL-Backend für Logging.
  • n8n läuft in einem separaten Docker-Container (ebenfalls via Coolify).
  • Cloudflare dient ausschließlich als DNS-Nameserver (Graue Wolke, kein Proxy).

Die Herausforderung: n8n und LiteLLM laufen in unterschiedlichen Docker-Netzwerken. Interne DNS-Namen wie litellm:4000 funktionieren nur innerhalb desselben Compose-Stacks. Also musste ich den Proxy über die öffentliche Domain (ai.syfrt.de) erreichbar machen – mit TLS und ohne interne Ports offenzulegen.

Schritt 1: LiteLLM-Proxy produktiv schalten

Die docker-compose.yml für LiteLLM sah bereits so aus:

services:
  litellm:
    image: ghcr.io/berriai/litellm:main-latest
    container_name: litellm
    environment:
      DATABASE_URL: postgresql://litellm:${DB_PASSWORD}@postgres:5432/litellm
      LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
      OPENAI_API_KEY: ${OPENAI_API_KEY}
    volumes:
      - ./config.yaml:/app/config.yaml
    command: ["--config", "/app/config.yaml", "--port", "4000"]
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.litellm.rule=Host(`ai.syfrt.de`)"
      - "traefik.http.routers.litellm.entrypoints=websecure"
      - "traefik.http.routers.litellm.tls.certresolver=letsencrypt"
      - "traefik.http.services.litellm.loadbalancer.server.port=4000"
    networks:
      - traefik-public
      - litellm-internal

  postgres:
    image: postgres:16-alpine
    container_name: litellm-db
    environment:
      POSTGRES_USER: litellm
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: litellm
    volumes:
      - postgres-data:/var/lib/postgresql/data
    networks:
      - litellm-internal

networks:
  traefik-public:
    external: true
  litellm-internal:
    driver: bridge

volumes:
  postgres-data:

Wichtig: Das Label traefik.http.services.litellm.loadbalancer.server.port=4000 sagt Traefik, dass der Container intern auf Port 4000 lauscht. Traefik routet dann HTTPS-Traffic von ai.syfrt.de dorthin.

Die config.yaml definiert die Modelle:

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY
  - model_name: gpt-4o-mini
    litellm_params:
      model: openai/gpt-4o-mini
      api_key: os.environ/OPENAI_API_KEY

litellm_settings:
  success_callback: ["postgres"]
  failure_callback: ["postgres"]
  cache: true
  cache_params:
    type: "redis"

Ich hatte Redis noch nicht eingebunden (kommt später), also lief Caching erstmal im Memory. PostgreSQL loggt alle Requests in die Tabelle litellm_spending.

Schritt 2: n8n-Workflows umstellen

In n8n nutze ich den OpenAI-Node für verschiedene Workflows (z. B. automatische Zusammenfassungen, Sentiment-Analyse von Support-Tickets). Bisher stand dort:

  • Base URL: https://api.openai.com/v1
  • API Key: Mein OpenAI-Key aus den Credentials

Jetzt änderte ich die Base URL auf:

https://ai.syfrt.de/v1

Der API-Key bleibt der Original-OpenAI-Key, weil LiteLLM ihn transparent weiterleitet. Der Proxy prüft den Key gegen OpenAI und loggt die Anfrage in PostgreSQL.

Erster Stolperstein: Ich hatte vergessen, dass n8n die Base URL pro Credential speichert, nicht global. Also musste ich in den Credentials-Einstellungen (nicht im Node selbst) die Base URL ändern. Danach funktionierten alle Workflows sofort.

Schritt 3: Blog-Agent über Proxy routen

Der wöchentliche Blog-Agent läuft als Python-Script in einem separaten Docker-Container (ebenfalls via Coolify). Er nutzt die openai-Library:

import openai

client = openai.OpenAI(
    api_key=os.getenv("OPENAI_API_KEY"),
    base_url="https://ai.syfrt.de/v1"
)

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "system", "content": system_prompt}, ...]
)

Die Umstellung war trivial: base_url auf ai.syfrt.de setzen, fertig. Der Agent merkt nicht, dass er über einen Proxy läuft.

Zweiter Stolperstein: Beim ersten Test kam ein SSLError: certificate verify failed. Grund: Der Container hatte keine aktuellen Root-Zertifikate. Fix:

RUN apk add --no-cache ca-certificates

Danach lief es.

Logging und Monitoring: PostgreSQL-Abfragen

Jetzt konnte ich endlich alle AI-Anfragen zentral auswerten. LiteLLM schreibt pro Request einen Eintrag in litellm_spending:

SELECT 
  model, 
  COUNT(*) as requests, 
  SUM(spend) as total_cost,
  AVG(response_time_ms) as avg_latency
FROM litellm_spending
WHERE startTime > NOW() - INTERVAL '7 days'
GROUP BY model
ORDER BY total_cost DESC;

Ergebnis nach einer Woche:

modelrequeststotal_costavg_latency
gpt-4o470.23 USD1850 ms
gpt-4o-mini3120.08 USD620 ms

Der Blog-Agent nutzt gpt-4o (wegen der langen Prompts), die n8n-Workflows laufen fast alle auf gpt-4o-mini. Latenz ist akzeptabel, Kosten überschaubar.

Caching: Identische Prompts nur einmal senden

LiteLLM unterstützt Semantic Caching: Wenn ein Prompt bereits gestellt wurde, wird die Antwort aus dem Cache geladen statt OpenAI erneut zu fragen. Das spart Kosten und Latenz.

Ich aktivierte Redis als Cache-Backend:

services:
  redis:
    image: redis:7-alpine
    container_name: litellm-redis
    networks:
      - litellm-internal

In der config.yaml:

litellm_settings:
  cache: true
  cache_params:
    type: "redis"
    host: "redis"
    port: 6379
    ttl: 3600

Nach einem Neustart des Stacks (docker compose up -d) sah ich im Log:

[INFO] Cache hit for prompt hash a3f7b2...
[INFO] Returning cached response (saved 0.02 USD)

Besonders bei n8n-Workflows, die wiederkehrende Analysen fahren (z. B. “Fasse diese Support-Anfrage zusammen”), zahlt sich das aus.

Fallback-Logik: Anthropic als Backup

Ein weiterer Vorteil von LiteLLM: Ich kann Fallback-Provider definieren. Falls OpenAI mal nicht erreichbar ist, springt automatisch Anthropic ein.

In der config.yaml:

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_key: os.environ/OPENAI_API_KEY
      fallbacks:
        - model: anthropic/claude-3-5-sonnet-20241022
          api_key: os.environ/ANTHROPIC_API_KEY

Ich habe das noch nicht produktiv getestet (OpenAI war bisher stabil), aber die Infrastruktur steht.

Lessons Learned

  1. Docker-Netzwerke sind nicht transparent: Interne DNS-Namen funktionieren nur innerhalb desselben Stacks. Für Service-zu-Service-Kommunikation über Stack-Grenzen hinweg braucht es Traefik und öffentliche Domains.
  2. Base URL in n8n ist Credential-gebunden: Die Umstellung muss in den Credentials erfolgen, nicht im Node.
  3. Root-Zertifikate in Alpine-Containern: ca-certificates muss explizit installiert werden, sonst schlagen HTTPS-Requests fehl.
  4. Logging ist Gold wert: PostgreSQL-basiertes Request-Logging gibt mir Transparenz über Kosten, Latenz und Nutzungsmuster – ohne externe SaaS-Tools.

Fazit: Ein Gateway, alle Workflows

Die Umstellung auf einen zentralen AI-Gateway war aufwendiger als gedacht (Docker-Netzwerke, TLS, Caching), aber das Ergebnis ist solide: Alle n8n-Workflows und der Blog-Agent laufen jetzt über ai.syfrt.de, ich habe vollständige Kontrolle über Logging, Caching und Fallback-Logik – und könnte jederzeit auf andere Provider oder lokale Modelle umschalten, ohne einen einzigen Workflow anzufassen.

Nächster Schritt: Rate Limiting pro API-Key einbauen, damit ich externe Nutzer (z. B. Kunden-Webhooks) sauber isolieren kann. Aber das ist ein Thema für die nächste Woche.

Häufige Fragen

Warum ein zentraler AI-Gateway statt direkter OpenAI-Aufrufe?

Ein zentraler Proxy (LiteLLM) ermöglicht einheitliches Logging, Caching, Fallback-Logik und Kostenkontrolle über alle Workflows hinweg – ohne dass jeder n8n-Node einzeln konfiguriert werden muss.

Wie bindet man n8n-Workflows an einen selbst gehosteten LiteLLM-Proxy?

Im OpenAI-Node von n8n wird die Base URL auf https://ai.syfrt.de/v1 gesetzt, der API-Key bleibt der Original-OpenAI-Key. LiteLLM leitet die Anfrage transparent weiter und loggt sie in PostgreSQL.

Welche Stolperfallen gibt es beim Routing über Docker-Netzwerke?

Interne Docker-DNS-Namen (z. B. litellm:4000) funktionieren nur innerhalb desselben Compose-Stacks. Für n8n-Workflows, die in separaten Containern laufen, muss der Proxy über Traefik und öffentliche Domain (ai.syfrt.de) erreichbar sein.


Fragen zum Thema? Schreiben Sie mir – ich antworte in der Regel innerhalb eines Werktags.