Technischer Leitfaden

Docker-Deployment für KI-Anwendungen: Produktionsmuster die skalieren

R
Rogue AI
··12 Min. Lesezeit

KI-Anwendungen in der Produktion zu betreiben ist ein anderes Problem als ein Modell zu trainieren oder ein Notebook auszufuehren. Sie brauchen zuverlaessige Prozessisolierung, Health-Monitoring, Ressourcenlimits, Sicherheitshaertung und die Moeglichkeit, Modelle ohne Ausfallzeiten zu aktualisieren. Docker loest die meisten dieser Probleme — aber nur, wenn man es richtig einsetzt. Wir betreiben 82 Container fuer ueber 20 KI-Anwendungen in der Produktion. Dieser Leitfaden behandelt die Muster, die funktionieren, die, die nicht funktionieren, und die spezifischen Konfigurationen fuer den Betrieb von LLM-basierten Anwendungen, Ollama-Modellservern und Multi-Container-KI-Stacks.

Warum Docker fuer KI-Anwendungen

Das Kernargument fuer Docker beim KI-Deployment ist Reproduzierbarkeit. Eine KI-Anwendung haengt typischerweise von einer bestimmten Python- oder Node.js-Laufzeitversion ab, von spezifischen Bibliotheksversionen (oft mit CUDA-spezifischen Builds), Modelldateien, die mehrere Gigabyte gross sein koennen, und Konfigurationen, die sich zwischen Entwicklung und Produktion unterscheiden. Docker packt all das in ein einzelnes Artefakt, das ueberall identisch laeuft. "Auf meinem Rechner funktioniert es" ist keine Ausrede mehr.

Ueber 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). Fuer Teams, die mehrere KI-Services verwalten — was zunehmend ueblich wird, da Organisationen zweckgebundene Modelle fuer 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-Groesse (kritisch wenn Images Modelldateien enthalten), trennt Build-Abhaengigkeiten von Laufzeit-Abhaengigkeiten und stellt sicher, dass der Produktionscontainer als Nicht-Root-Benutzer laeuft. Hier ist das Muster:

Vier-Stufen-Build-Muster

  • Stufe 1 — base:Alpine-basiertes Node.js-Image (node:22-alpine3.21) mit Systemabhaengigkeiten 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 aendern sich unter Ihnen.
  • Stufe 2 — deps: Anwendungsabhaengigkeiten installieren mit npm ci (nicht npm install — ci ist deterministisch aus der Lockfile). Diese Stufe wird gecacht, solange sich package-lock.json nicht aendert, was Rebuilds schnell macht.
  • Stufe 3 — builder: Quellcode kopieren, Prisma generate ausfuehren (falls Datenbank genutzt wird) und den Produktions-Build starten (next build fuer Next.js-Apps). Das Ergebnis ist die kompilierte Anwendung ohne Quelldateien oder Dev-Abhaengigkeiten.
  • 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 enthaelt nur das, was zum Betrieb noetig ist — keine Build-Tools, kein Quellcode, keine Dev-Abhaengigkeiten.

Das Ergebnis ist ein Produktionsimage von typischerweise 150-300 MB fuer 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 Angriffsflaeche.

Health Checks: Die am meisten uebersehene Konfiguration

Health Checks bestimmen, ob Docker einen Container als "gesund" einstuft und Traffic dorthin leitet. Ohne Health Checks hat Docker keine Moeglichkeit zu wissen, ob Ihre Anwendung abgestuerzt ist, haengt oder in einer Endlosschleife steckt. Es sieht nur, dass der Prozess laeuft. 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 koennen.

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

  • Anwendungscontainer: HTTP-Health-Endpunkt per wget. Der Endpunkt prueft, 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. Prueft, ob die Datenbank Verbindungen auf dem erwarteten Port annimmt. Das ist zuverlaessiger als ein TCP-Port-Check, weil pg_isready das Postgres-Protokoll verifiziert.
  • Redis: redis-cli ping. Gibt PONG zurueck, wenn Redis funktioniert. Einfach, schnell, zuverlaessig.
  • Ollama (Modellserver): HTTP-Check gegen den Ollama-API-Health-Endpunkt. Bestaetigt, dass Ollama laeuft und mindestens ein Modell geladen und bereit fuer Inferenz ist — nicht nur, dass der Prozess gestartet wurde.

Wichtig: Nutzen Sie 127.0.0.1, niemals localhost

Alpine Linux loest "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 staendig 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 tatsaechlich bereit sind.

Ollama in Docker: Lokale LLMs in der Produktion betreiben

Ollama ist zum Standard fuer das Serving lokaler LLMs geworden, und der Betrieb in Docker ist unkompliziert — mit ein paar wichtigen Ueberlegungen fuer den Produktionseinsatz.

Geteilte Modellserver-Architektur

Wir betreiben eine einzelne Ollama-Instanz, die mehrere Anwendungen bedient. Jede KI-Anwendung verbindet sich ueber 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 ueber Anfragen hinweg geteilt werden. Mit einer 24-GB-GPU koennen Sie ein 13B-Parameter-Modell problemlos fuer fuenf 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 ueber seinen Containernamen (ailab-ollama) und den Standardport (11434). Kein Host-Networking, keine Port-Veroeffentlichung zum Host — der gesamte Traffic bleibt innerhalb des Docker-Netzwerks.

GPU-Passthrough-Konfiguration

Fuer GPU-Inferenz (was Sie wollen — CPU-Inferenz auf grossen Modellen ist fuer 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 fuegen Sie die GPU-Reservierung in Ihrer Docker-Compose-Datei unter dem deploy-Abschnitt hinzu.

Wichtige Ueberlegungen fuer GPU-faehige 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. Fuer Multi-Anwendungs-Setups, die dasselbe Modell nutzen, ist dieser Standard in Ordnung. Fuer Setups mit verschiedenen Modellen erhoehen Sie den Wert basierend auf dem verfuegbaren GPU-Speicher.
  • Modellspeicherung: Mounten Sie ein persistentes Volume fuer Ollamas Modellverzeichnis. Modelle koennen 4-15 GB gross sein — die sollen nicht bei jedem Container-Neustart heruntergeladen werden. Das Volume-Mount stellt sicher, dass Modelle ueber Neustarts und Updates hinweg bestehen bleiben.
  • Parallele Anfragen: Setzen Sie OLLAMA_NUM_PARALLEL, um zu steuern, wie viele Anfragen Ollama gleichzeitig verarbeitet. Hoehere Werte erhoehen den Durchsatz, verbrauchen aber mehr GPU-Speicher. Wir nutzen typischerweise 2-4 je nach Modellgroesse und GPU-Kapazitaet.

Sicherheitshaertung: Produktionsreife Container-Sicherheit

KI-Anwendungen in der Produktion zu betreiben bedeutet, dass sie Angriffsflaechen sind. Ein falsch konfigurierter Container kann Modellausgaben leaken, interne APIs exponieren oder einen Einstiegspunkt fuer laterale Bewegung in Ihrem Netzwerk bieten. Sicherheitshaertung ist nicht optional. Hier ist unsere Standardkonfiguration, die auf jeden Container angewendet wird.

HaertungsmassnahmeKonfigurationWarum es wichtig ist
Alle Capabilities entfernencap_drop: [ALL]Verhindert Privilege Escalation. Nur Benoetigtes gezielt zurueckgeben.
Keine neuen Privilegiensecurity_opt: [no-new-privileges:true]Blockiert setuid/setgid-Binaries bei der Erlangung erhoehter 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 fuer /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 fuellen. 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 Aenderungen 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 zusaetzliche Zeilen in docker-compose.yml), und die Sicherheitsverbesserung ist erheblich. Die meisten Container-Sicherheitsvorfaelle, die wir in freier Wildbahn gesehen haben, waeren durch diese grundlegenden Massnahmen verhindert worden.

Netzwerkarchitektur: Isolierung ohne Komplexitaet

Jede Anwendung bekommt ihr eigenes Docker-Netzwerk. Der Anwendungscontainer, seine Datenbank und seine Redis-Instanz teilen sich ein Netzwerk. Sie koennen 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 ueber 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 fuer 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 fuer 5-30 Sekunden in die Hoehe treiben und bei GPU-Nutzung den GPU-Speicher fuer 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. Fuer 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. Fuer den Ollama-Container haengen die Limits von der Modellgroesse 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 tatsaechlichen 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 (fuer externe Services), Modellkonfigurationsparameter und anwendungsspezifische Einstellungen. Hardcodieren Sie diese niemals in Dockerfiles oder docker-compose.yml-Dateien. Wir nutzen .env-Dateien fuer Entwicklung und umgebungsspezifische Konfiguration, wobei erforderliche Variablen mit der ${VAR:?Fehlermeldung}-Syntax in docker-compose.yml erzwungen werden. Das bedeutet, docker compose up schlaegt sofort mit einer klaren Fehlermeldung fehl, wenn eine erforderliche Variable fehlt — statt mit kaputter Konfiguration zu starten.

Fuer Produktions-Secrets (Datenbankpasswoerter, 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 koennen, 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. Fuer die meisten unserer Anwendungen dauert dieser gesamte Prozess 60-90 Sekunden bei null verlorenen Anfragen.

Modell-Updates ohne Redeployment

Ollama unterstuetzt 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 aendert sich. Diese Trennung von Anwendungscode und Modellartefakten ist ein grosser Vorteil der geteilten Ollama-Architektur.

Monitoring und Observability

Fuer eine Flotte von KI-Containern brauchen Sie Sichtbarkeit in vier Dimensionen:

  • Container-Health: Laeuft 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 (fuer wiederholte Prompts) und Ausgabequalitaets-Scores, falls Sie eine Moeglichkeit 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 Zustaenden alarmieren, bevor sie zu Ausfaellen werden.

Haeufige Fehler und wie Sie sie vermeiden

  • :latest-Tags in der Produktion nutzen. Ihr Build ist nicht reproduzierbar, wenn sich das Base-Image zwischen Pulls aendern kann. Pinnen Sie auf spezifische Patch-Versionen. Ja, das bedeutet manuelle Updates — das ist der Punkt. Sie wollen kontrollieren, wann Aenderungen 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 zusaetzlichen Konfigurationszeilen sind die Sicherheitsverbesserung wert.
  • Keine Ressourcenlimits. Funktioniert prima, bis es das nicht mehr tut. Ein ausser Kontrolle geratener Prozess, ein Speicherleck, eine aussergewoehnlich grosse Inferenzanfrage — und ploetzlich reagiert Ihr gesamter Server nicht mehr, weil ein einzelner Container alle verfuegbaren Ressourcen verbraucht hat.
  • Ports auf 0.0.0.0 publizieren. Das exponiert Ihren Container an jedes Netzwerk-Interface des Hosts. Wenn der Host eine oeffentliche 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), koennen waehrend der Inferenz haengen und neigen zu Speicherlecks. Ohne Health Checks hat Docker keine Moeglichkeit, diese Zustaende 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 uebersieht.

Erste Schritte

Wenn Sie KI-Anwendungen deployen und mit Zuverlaessigkeit, Ressourcenmanagement oder Sicherheit kaempfen, loest Docker mit den hier beschriebenen Mustern die meisten dieser Probleme. Die initiale Investition sind ein paar Tage Infrastrukturarbeit, die sich ueber Jahre in operativer Stabilitaet auszahlen.

Wir bieten KI-Infrastruktur-Beratung fuer Teams, die es beim ersten Mal richtig machen wollen. Wir pruefen Ihr aktuelles Deployment, identifizieren Sicherheits- und Zuverlaessigkeitsluecken und implementieren produktionsreife Docker-Konfigurationen fuer Ihren KI-Stack.

Kostenlosen Discovery Call buchen um Ihre KI-Deployment-Herausforderungen zu besprechen. Kein Verkaufsgespraech — nur eine praktische Einschaetzung, wo Ihre Infrastruktur steht und welche Verbesserungen den groessten Einfluss haetten.

Rogue AI • Production Systems •