Zum Hauptinhalt springen
Startseite / Blog / Technischer Leitfaden
Technischer Leitfaden

Docker für produktive KI: Muster, die Last aushalten

RRogue AI··12 Min. Lesezeit
Isometrische Flotte von Docker-Containern, die jeweils einen KI-Kern im Produktivbetrieb ausführen

Die meisten „Produktions“-KI-Setups fallen bei denselben drei Punkten durch: keine Resource-Limits, Secrets ins Image gebacken und ein Container, der als root läuft. Ein Modell dort laufen zu lassen, wo echter Traffic auftrifft, reproduzierbar, beobachtbar, abgeriegelt, neustart-sicher, ist ein Sicherheits- und Betriebsproblem, bevor es ein KI-Problem ist. Das sind die Docker-Muster für eine selbst gehostete Flotte, jeder Container standardmäßig gehärtet — dieselbe Disziplin, mit der Enterprise-Netzwerke abgesichert werden.

Warum Docker für KI-Anwendungen

Das Kernargument für Docker beim KI-Deployment ist Reproduzierbarkeit. Eine KI-Anwendung pinnt typischerweise eine bestimmte Python- oder Node.js-Laufzeit, spezifische Bibliotheksversionen (oft mit CUDA-spezifischen Builds), Modelldateien von mehreren Gigabyte und Konfigurationen, die zwischen Entwicklung und Produktion auseinanderdriften. Docker packt all das in ein einzelnes Artefakt, das überall identisch läuft, und „auf meinem Rechner funktioniert es“ ist keine Ausrede mehr.

Über Reproduzierbarkeit hinaus bietet Docker Prozessisolierung (ein Container-Absturz reisst nicht Ihre anderen Services mit), Ressourcenlimits (verhindert, dass eine ausser Kontrolle geratene Inferenzschleife den gesamten Speicher verbraucht) und deklarative Infrastruktur (Ihr gesamtes Deployment ist in Code definiert, versioniert und reviewbar). Für Teams, die mehrere KI-Services verwalten, was zunehmend üblich wird, da Organisationen zweckgebundene Modelle für verschiedene Aufgaben einsetzen, sind diese Eigenschaften nicht optional.

Multi-Stage Dockerfiles: Das Vier-Stufen-Muster

Jede KI-Anwendung, die wir deployen, nutzt ein Vier-Stufen-Dockerfile. Dieses Muster minimiert die finale Image-Größe (kritisch wenn Images Modelldateien enthalten), trennt Build-Abhängigkeiten von Laufzeit-Abhängigkeiten und stellt sicher, dass der Produktionscontainer als Nicht-Root-Benutzer läuft. Hier ist das Muster:

Vier-Stufen-Build-Muster

  • Stufe 1, base:Alpine-basiertes Node.js-Image (node:22-alpine3.21) mit Systemabhängigkeiten und Sicherheitsupdates (apk upgrade). Das ist die Grundlage, die Build und Laufzeit teilen. Pinnen Sie auf Patch-Version, nutzen Sie niemals "latest" oder auch nur "22-alpine", Image-Inhalte ändern sich unter Ihnen.
  • Stufe 2, deps: Anwendungsabhängigkeiten installieren mit npm ci (nicht npm install, ci ist deterministisch aus der Lockfile). Diese Stufe wird gecacht, solange sich package-lock.json nicht ändert, was Rebuilds schnell macht.
  • Stufe 3, builder: Quellcode kopieren, Prisma generate ausführen (falls Datenbank genutzt wird) und den Produktions-Build starten (next build für Next.js-Apps). Das Ergebnis ist die kompilierte Anwendung ohne Quelldateien oder Dev-Abhängigkeiten.
  • Stufe 4, runner: Nur den Standalone-Build in ein sauberes Base-Image kopieren. Nicht-Root-Benutzer erstellen (uid 1001, gid 1001). Dateiberechtigungen setzen. Entrypoint konfigurieren. Das finale Image enthält nur das, was zum Betrieb nötig ist, keine Build-Tools, kein Quellcode, keine Dev-Abhängigkeiten.

Das Ergebnis ist ein Produktionsimage von typischerweise 150-300 MB für eine Next.js-KI-Anwendung, verglichen mit 1-2 GB, wenn Sie einfach alles in eine einzelne Stufe kopieren. Kleinere Images bedeuten schnellere Deployments, schnellere Rollbacks und weniger Angriffsfläche.

Health Checks: Die am meisten übersehene Konfiguration

Health Checks bestimmen, ob Docker einen Container als "gesund" einstuft und Traffic dorthin leitet. Ohne Health Checks hat Docker keine Möglichkeit zu wissen, ob Ihre Anwendung abgestürzt ist, hängt oder in einer Endlosschleife steckt. Es sieht nur, dass der Prozess läuft. Das ist besonders problematisch bei KI-Anwendungen, wo das Laden von Modellen 30-60 Sekunden nach dem Containerstart dauern kann und Speicherlecks durch wiederholte Inferenz einen Service schleichend verschlechtern können.

Jeder Service in unserem Stack hat einen Health Check. Hier die Muster nach Service-Typ:

  • Anwendungscontainer: HTTP-Health-Endpunkt per wget. Der Endpunkt prüft, ob die Anwendung Anfragen annehmen kann und ob alle erforderlichen nachgelagerten Services (Datenbank, Modellserver) erreichbar sind. Intervall: 30s, Timeout: 10s, Retries: 3.
  • PostgreSQL: pg_isready-Befehl. Prüft, ob die Datenbank Verbindungen auf dem erwarteten Port annimmt. Das ist zuverlässiger als ein TCP-Port-Check, weil pg_isready das Postgres-Protokoll verifiziert.
  • Redis: redis-cli ping. Gibt PONG zurück, wenn Redis funktioniert. Einfach, schnell, zuverlässig.
  • Ollama (Modellserver): HTTP-Check gegen den Ollama-API-Health-Endpunkt. Bestätigt, dass Ollama läuft und mindestens ein Modell geladen und bereit für Inferenz ist, nicht nur, dass der Prozess gestartet wurde.

Wichtig: Nutzen Sie 127.0.0.1, niemals localhost

Alpine Linux löst "localhost" zu IPv6 (::1) auf, bevor es IPv4 (127.0.0.1) versucht. Wenn Ihr Service nur auf IPv4 lauscht, was bei den meisten Node.js- und Python-Anwendungen Standard ist, schlagen Health Checks gegen "localhost" intermittierend oder dauerhaft fehl. Nutzen Sie immer explizit 127.0.0.1. Dieses eine Problem verursacht mehr "Container startet ständig neu"-Tickets als jede andere einzelne Ursache in unserer Flotte.

Kombinieren Sie Health Checks mit depends_on-Bedingungen. Nutzen Sie niemals blosse depends_on (das wartet nur darauf, dass der Container startet, nicht dass er gesund wird). Nutzen Sie immer depends_on mit condition: service_healthy. So stellen Sie sicher, dass Ihr Anwendungscontainer nicht startet, bevor Datenbank und Modellserver tatsächlich bereit sind.

Ollama in Docker: Lokale LLMs in der Produktion betreiben

Ollama ist zum Standard für das Serving lokaler LLMs geworden, und der Betrieb in Docker ist unkompliziert, mit ein paar wichtigen Überlegungen für den Produktionseinsatz.

Geteilte Modellserver-Architektur

Wir betreiben eine einzelne Ollama-Instanz, die mehrere Anwendungen bedient. Jede KI-Anwendung verbindet sich über ein dediziertes Docker-Netzwerk mit dem gemeinsamen Ollama-Container. Dieser Ansatz ist effizienter als separate Ollama-Instanzen pro Anwendung, weil Modellgewichte einmal in den GPU-Speicher geladen und über Anfragen hinweg geteilt werden. Mit einer 24-GB-GPU können Sie ein 13B-Parameter-Modell problemlos für fünf oder sechs Anwendungen gleichzeitig bereitstellen.

Die Docker-Compose-Konfiguration nutzt ein benanntes externes Netzwerk (wir nennen es ailab-network), dem sowohl der Ollama-Container als auch alle nutzenden Anwendungscontainer beitreten. Anwendungscontainer referenzieren Ollama über seinen Containernamen (ailab-ollama) und den Standardport (11434). Kein Host-Networking, keine Port-Veröffentlichung zum Host, der gesamte Traffic bleibt innerhalb des Docker-Netzwerks.

GPU-Passthrough-Konfiguration

Für GPU-Inferenz (was Sie wollen, CPU-Inferenz auf großen Modellen ist für den Produktionseinsatz unbrauchbar langsam) braucht Docker Zugriff auf die Host-GPU. Unter Linux mit NVIDIA-GPUs erfordert das das NVIDIA Container Toolkit (nvidia-container-toolkit-Paket). Nach der Installation fügen Sie die GPU-Reservierung in Ihrer Docker-Compose-Datei unter dem deploy-Abschnitt hinzu.

Wichtige Überlegungen für GPU-fähige Container:

  • Speicherverwaltung: Setzen Sie OLLAMA_MAX_LOADED_MODELS, um zu steuern, wie viele Modelle gleichzeitig im GPU-Speicher bleiben. Standard ist 1, was bedeutet, dass nur das zuletzt genutzte Modell geladen bleibt. Für Multi-Anwendungs-Setups, die dasselbe Modell nutzen, ist dieser Standard in Ordnung. Für Setups mit verschiedenen Modellen erhöhen Sie den Wert basierend auf dem verfügbaren GPU-Speicher.
  • Modellspeicherung: Mounten Sie ein persistentes Volume für Ollamas Modellverzeichnis. Modelle können 4-15 GB groß sein, die sollen nicht bei jedem Container-Neustart heruntergeladen werden. Das Volume-Mount stellt sicher, dass Modelle über Neustarts und Updates hinweg bestehen bleiben.
  • Parallele Anfragen: Setzen Sie OLLAMA_NUM_PARALLEL, um zu steuern, wie viele Anfragen Ollama gleichzeitig verarbeitet. Höhere Werte erhöhen den Durchsatz, verbrauchen aber mehr GPU-Speicher. Wir nutzen typischerweise 2-4 je nach Modellgröße und GPU-Kapazität.

Sicherheitshärtung: Produktionsreife Container-Sicherheit

KI-Anwendungen in der Produktion zu betreiben bedeutet, dass sie Angriffsflächen sind. Ein falsch konfigurierter Container kann Modellausgaben leaken, interne APIs exponieren oder einen Einstiegspunkt für laterale Bewegung in Ihrem Netzwerk bieten. Sicherheitshärtung ist nicht optional. Hier ist unsere Standardkonfiguration, die auf jeden Container angewendet wird.

HärtungsmaßnahmeKonfigurationWarum es wichtig ist
Alle Capabilities entfernencap_drop: [ALL]Verhindert Privilege Escalation. Nur Benötigtes gezielt zurückgeben.
Keine neuen Privilegiensecurity_opt: [no-new-privileges:true]Blockiert setuid/setgid-Binaries bei der Erlangung erhöhter Rechte.
Nicht-Root-BenutzerUSER nextjs (uid 1001)Begrenzt den Schadensradius bei Container-Kompromittierung.
Nur-Lese-Dateisystemread_only: true + tmpfs-MountsVerhindert Schreiben ins Container-Dateisystem. Tmpfs für /tmp und Caches.
Ressourcenlimitsdeploy.resources.limits (Memory + CPU)Verhindert, dass ein einzelner Container alle Host-Ressourcen verbraucht.
Prozesslimitsdeploy.resources.limits.pids: 200Verhindert Fork-Bombs oder unkontrolliertes Prozess-Spawning.
Log-Rotationmax-size: 10m, max-file: 3Verhindert, dass Logdateien die Festplatte füllen. DB/Redis: 50m/5.
NetzwerkisolierungDediziertes Netzwerk pro App, 127.0.0.1-BindingContainer sehen nur die Services, die sie brauchen. Keine Exposition zum Host.
Gepinnte Imagespostgres:16.11-alpine3.21, nicht :latestVerhindert unerwartete Änderungen beim Image-Pull. Reproduzierbare Builds.

Das sind keine aspirativen Best Practices, das ist unsere Standardkonfiguration, die auf jeden Container in der Produktion angewendet wird. Der Overhead ist minimal (ein paar zusätzliche Zeilen in docker-compose.yml), und die Sicherheitsverbesserung ist erheblich. Die meisten Container-Sicherheitsvorfälle, die wir in freier Wildbahn gesehen haben, wären durch diese grundlegenden Maßnahmen verhindert worden.

Netzwerkarchitektur: Isolierung ohne Komplexität

Jede Anwendung bekommt ihr eigenes Docker-Netzwerk. Der Anwendungscontainer, seine Datenbank und seine Redis-Instanz teilen sich ein Netzwerk. Sie können innerhalb dieses Netzwerks frei kommunizieren, erreichen aber keine Container in anderen Anwendungsnetzwerken. Die einzige gemeinsame Ressource ist der Ollama-Modellserver, der in seinem eigenen Netzwerk sitzt, dem Anwendungscontainer explizit beitreten.

Diese Architektur bedeutet: Eine Kompromittierung eines Anwendungscontainers gibt keinen Zugriff auf die Datenbanken anderer Anwendungen. Ausserdem sind die Netzwerkrichtlinien einfach: Alles innerhalb des Anwendungsnetzwerks erlauben, Verbindungen zum gemeinsamen Ollama-Netzwerk erlauben, alles andere verweigern.

Port-Binding erfolgt ausschliesslich auf 127.0.0.1. Kein Container-Port wird jemals auf 0.0.0.0 (alle Interfaces) publiziert. Externer Zugriff wird über einen Reverse Proxy (Caddy oder Nginx) abgewickelt, der TLS terminiert und an die localhost-gebundenen Container-Ports weiterleitet. Das bedeutet, Container sind nicht direkt aus dem Netzwerk erreichbar, selbst wenn Firewall-Regeln falsch konfiguriert sind.

Ressourcenmanagement für KI-Workloads

KI-Anwendungen haben andere Ressourcenprofile als typische Web-Services. Eine Inferenzanfrage kann 2-8 GB RAM verbrauchen (je nach Modell), die CPU-Auslastung für 5-30 Sekunden in die Höhe treiben und bei GPU-Nutzung den GPU-Speicher für die gesamte Dauer belegen. Ohne Ressourcenlimits kann ein Burst von Inferenzanfragen OOM-Kills bei parallel betriebenen Services verursachen.

Wir setzen explizite Memory- und CPU-Limits auf jeden Container. Für KI-Anwendungscontainer sind typische Limits 512 MB bis 1 GB RAM und 1-2 CPU-Kerne, die Anwendung selbst braucht nicht viel, weil die Inferenz im Ollama-Container stattfindet. Für den Ollama-Container hängen die Limits von der Modellgröße ab: ein 13B-Modell braucht etwa 10-12 GB RAM wenn geladen. PostgreSQL bekommt 256-512 MB pro Datenbankinstanz. Redis bekommt 128-256 MB.

Erst messen, dann limitieren

Raten Sie nicht bei Ressourcenlimits. Lassen Sie Ihre Anwendung eine Woche unter realistischer Last laufen, beobachten Sie den tatsächlichen Verbrauch mit docker stats, und setzen Sie die Limits dann auf das 1,5-fache des beobachteten Peaks. Zu enge Limits verursachen schwer debugbare OOM-Kills. Zu lockere verschwenden Ressourcen und bieten keinen Schutz. Erst messen.

Umgebungsvariablen und Secrets

KI-Anwendungen brauchen typischerweise Datenbankzugangsdaten, API-Keys (für externe Services), Modellkonfigurationsparameter und anwendungsspezifische Einstellungen. Hardcodieren Sie diese niemals in Dockerfiles oder docker-compose.yml-Dateien. Wir nutzen .env-Dateien für Entwicklung und umgebungsspezifische Konfiguration, wobei erforderliche Variablen mit der ${VAR:?Fehlermeldung}-Syntax in docker-compose.yml erzwungen werden. Das bedeutet, docker compose up schlägt sofort mit einer klaren Fehlermeldung fehl, wenn eine erforderliche Variable fehlt, statt mit kaputter Konfiguration zu starten.

Für Produktions-Secrets (Datenbankpasswörter, API-Keys) nutzen wir Docker Secrets oder mounten Dateien vom Host. Die .env-Datei wird nie in die Versionskontrolle eingecheckt. Eine .env.example-Datei listet alle erforderlichen Variablen mit Platzhalterwerten.

Deployment- und Update-Muster

Zero-Downtime-Updates

Bei KI-Anwendungen, bei denen Inferenzanfragen 10-30 Sekunden dauern können, bedeutet ein naiver Neustart (alten Container stoppen, neuen starten) verlorene Anfragen. Unser Update-Verfahren: Neues Image bauen, neuen Container neben dem alten starten, warten bis der neue Container gesund wird (Health Check bestanden), Reverse Proxy auf den neuen Container umschalten, Verbindungen vom alten Container abfliessen lassen, dann den alten Container stoppen. Für die meisten unserer Anwendungen dauert dieser gesamte Prozess 60-90 Sekunden bei null verlorenen Anfragen.

Modell-Updates ohne Redeployment

Ollama unterstützt das Ziehen neuer Modellversionen zur Laufzeit. Wenn wir ein Modell aktualisieren, ziehen wir die neue Version in den laufenden Ollama-Container, verifizieren, dass sie korrekt geladen wird, und aktualisieren dann die Anwendungskonfiguration, um den neuen Modell-Tag zu nutzen. Der Anwendungscontainer muss nicht neugestartet werden, nur die Modellreferenz ändert sich. Diese Trennung von Anwendungscode und Modellartefakten ist ein großer Vorteil der geteilten Ollama-Architektur.

Monitoring und Observability

Für eine Flotte von KI-Containern brauchen Sie Sichtbarkeit in vier Dimensionen:

  • Container-Health: Läuft der Container? Besteht er den Health Check? Wann war der letzte Neustart und warum?
  • Ressourcennutzung: CPU-, Memory-, Disk- und GPU-Auslastung im Zeitverlauf. Trendlinien sind wichtiger als Einzelwerte, Sie wollen das Speicherleck erkennen, bevor es einen OOM-Kill verursacht.
  • Anwendungsmetriken:Request-Latenz, Fehlerraten, Inferenzzeit, Queue-Tiefe. Diese verraten Ihnen, ob die Anwendung korrekt arbeitet, selbst wenn der Container "gesund" ist.
  • Modellmetriken: Token-Durchsatz, Modell-Ladezeit, Cache-Hit-Rate (für wiederholte Prompts) und Ausgabequalitäts-Scores, falls Sie eine Möglichkeit haben, diese zu messen.

Wir nutzen eine Kombination aus Dockers eingebautem Logging (mit Rotation), anwendungsseitigem strukturiertem Logging (JSON-Format, an einen zentralen Log-Speicher geliefert) und periodischen Health-Check-Skripten, die bei verschlechterten Zuständen alarmieren, bevor sie zu Ausfällen werden.

Häufige Fehler und wie Sie sie vermeiden

  • :latest-Tags in der Produktion nutzen. Ihr Build ist nicht reproduzierbar, wenn sich das Base-Image zwischen Pulls ändern kann. Pinnen Sie auf spezifische Patch-Versionen. Ja, das bedeutet manuelle Updates, das ist der Punkt. Sie wollen kontrollieren, wann Änderungen passieren.
  • Als root laufen. Der Standard. Und das bedeutet, ein Container-Ausbruch gibt dem Angreifer Root-Zugriff auf den Host. Erstellen und nutzen Sie immer einen Nicht-Root-Benutzer in Ihrem Dockerfile. Die drei zusätzlichen Konfigurationszeilen sind die Sicherheitsverbesserung wert.
  • Keine Ressourcenlimits. Funktioniert prima, bis es das nicht mehr tut. Ein ausser Kontrolle geratener Prozess, ein Speicherleck, eine außergewöhnlich große Inferenzanfrage, und plötzlich reagiert Ihr gesamter Server nicht mehr, weil ein einzelner Container alle verfügbaren Ressourcen verbraucht hat.
  • Ports auf 0.0.0.0 publizieren. Das exponiert Ihren Container an jedes Netzwerk-Interface des Hosts. Wenn der Host eine öffentliche IP hat, ist Ihre Datenbank jetzt aus dem Internet erreichbar. Binden Sie immer an 127.0.0.1.
  • Health Checks bei KI-Containern weglassen. KI-Container haben lange Startzeiten (Modell laden), können während der Inferenz hängen und neigen zu Speicherlecks. Ohne Health Checks hat Docker keine Möglichkeit, diese Zustände zu erkennen und den Container neuzustarten. Ein Health Check, der verifiziert, dass das Modell geladen ist und antwortet, erkennt Probleme, die ein einfacher Prozess-Check übersieht.

Erste Schritte

Wenn Sie KI-Anwendungen deployen und mit Zuverlässigkeit, Ressourcenmanagement oder Sicherheit kämpfen, räumt Docker mit den Mustern oben die meisten dieser Probleme vom Tisch. Der Aufwand vorab sind ein paar Tage Infrastrukturarbeit, und der zahlt sich über Jahre in operativer Stabilität aus.

Wir bieten KI-Infrastruktur-Beratung für Teams, die es beim ersten Mal richtig machen wollen. Wir prüfen Ihr aktuelles Deployment, identifizieren Sicherheits- und Zuverlässigkeitslücken und implementieren produktionsreife Docker-Konfigurationen für Ihren KI-Stack.

Kostenlosen Discovery Call buchen um Ihre KI-Deployment-Herausforderungen zu besprechen. Kein Verkaufsgespräch, nur eine praktische Einschätzung, wo Ihre Infrastruktur steht und welche Verbesserungen den größten Einfluss hätten.

Weitere Artikel

Technischer Leitfaden

Die meisten Aufgaben brauchen keinen KI-Agenten. Nehmen Sie eine Pipeline.

10 Min. Lesezeit

Technischer Leitfaden

LLM-Funktionen bauen, die in der Produktion bestehen

11 Min. Lesezeit

← Alle Artikel