Selbst gehostete KI absichern: Infrastruktur-Härtung für die Produktion
Die meisten Texte zur KI-Sicherheit hören 2026 bei Prompt Injection und Content-Filtering auf. Das ist die Anwendungsschicht. Wer KI selbst betreibt — Ollama auf einem VPS, vLLM auf einem GPU-Server, irgendetwas, das eigene Infrastruktur berührt — hat eine deutlich größere Angriffsfläche darunter: Container, Netzwerke, Secrets, die Datenbank mit den Embeddings, die GPU-Runtime, die Build-Pipeline, die das Image erzeugt hat.
Dieser Leitfaden beschreibt, wie man diesen Stack absichert. Er stammt aus dem Betrieb einer selbst gehosteten KI-Flotte mit 20+ Anwendungen — Ollama für lokale Inferenz, PostgreSQL mit pgvector, Redis, eigene Next.js-Frontends, Caddy als Reverse Proxy — über isolierte Docker-Netzwerke auf Hetzner. Die meisten der unten genannten Fehler waren erst meine, bevor sie zu Lektionen wurden.
Das Bedrohungsmodell, dem Sie tatsächlich begegnen
Vor jeder Kontrolle muss klar sein, wogegen verteidigt wird. Selbst gehostete KI hat in der Praxis vier realistische Bedrohungsklassen:
Im Internet exponierte Dienste
Jeder öffentlich erreichbare Port ist innerhalb von Minuten ein Ziel. Ollama mit Default-Bind (0.0.0.0:11434), offenes Postgres, ungeschütztes Redis — alles wird laufend von automatisierten Scannern enumeriert und sondiert.
Container-Ausbruch und laterale Bewegung
Ein kompromittierter App-Container mit Default-Linux-Capabilities und Zugriff auf den Docker-Socket kann auf den Host oder auf Nachbarcontainer wechseln. Der Schadensradius ist der gesamte Host.
Supply-Chain-Kompromittierung
Nicht gepinnte Base-Images, ungeprüfte Hugging-Face-Gewichte, npm-Pakete mit Post-Install-Skripten, Python-Wheels mit nativem Code — jede Abhängigkeit ist eine Vertrauensentscheidung, die man oft unbewusst trifft.
Datenleck über das Modell
Prompt-Logs mit PII, Embeddings, die vertraulichen Text teilweise reversibel abbilden, Trainingsdaten, die auf Disk liegen bleiben, Modell-Dateien, die zwischen Mandanten geteilt werden. Die Daten liegen in Ihrer Umgebung, aber nicht unter Ihren Zugriffskontrollen.
Container-Härtung: Die nicht verhandelbaren Defaults
Dockers Defaults sind auf Entwicklerfreundlichkeit optimiert, nicht auf produktive Sicherheit. Jeder produktive Container in meiner Flotte hat Folgendes in docker-compose.yml — die Kosten sind nahe null, die Reduktion des Schadensradius enorm.
Alle Capabilities entziehen, dann nur Notwendiges hinzufügen
services:
app:
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
read_only: true
tmpfs:
- /tmp:size=100m
- /app/.next/cache:size=200m
user: "1001:1001"Die meisten App-Container brauchen keine einzige Capability. Postgres braucht CHOWN, DAC_OVERRIDE, FOWNER, SETGID, SETUID. Redis auf Alpine braucht CHOWN, DAC_OVERRIDE, SETGID, SETUID für den User-Switch beim Start. Alles andere ist blindes Übernehmen ohne Begründung.
Als Non-Root-User laufen lassen. Das Dockerfile legt einen numerischen User an (UID 1001) und wechselt vor dem Entrypoint dorthin. USER root in der finalen Stage ist ein Review-Flag. Wenn es unvermeidbar ist (manche Python ML-Images), das Capability-Set explizit pinnen und regelmäßig prüfen.
Dateisystem read-only setzen. read_only: true plus tmpfs für die wenigen schreibbaren Pfade (Cache, /tmp). Ein kompromittierter Prozess kann keine Webshell ablegen oder über Restarts persistieren. Beim ersten Aktivieren findet man garantiert eine App, die in /app/logs schreibt — die App fixen, nicht die Constraint lockern.
Ressourcengrenzen sind Pflicht. Ein entgleister LLM-Aufruf kann den Host per OOM abschießen. deploy.resources.limits.memory und deploy.resources.limits.cpus auf jedem Service setzen. pids_limit: 200 stoppt Fork-Bombs.
Netzwerk-Isolation: Jede App in ihrem eigenen Netzwerk
Die Docker-Default-Bridge legt jeden Container in dieselbe Broadcast-Domain. Ein kompromittierter Container kann mit jedem anderen Container auf dem Host sprechen. Tun Sie das nicht.
Eigenes Netzwerk pro App
Jeder Application Stack bekommt sein eigenes /24-Subnetz. App, Datenbank, Queue und Worker liegen auf demselben Netzwerk; nichts anderes. Eine Kompromittierung in einer App erreicht keine andere.
An 127.0.0.1 binden, niemals 0.0.0.0
Veröffentlichte Ports gehen an 127.0.0.1:PORT. Der Reverse Proxy (Caddy oder nginx) lauscht auf der öffentlichen Schnittstelle, terminiert TLS und proxied an localhost. Postgres, Redis und Ollama sind nie direkt aus dem Internet erreichbar.
Geteilte Dienste auf einem definierten geteilten Netzwerk
Eine Ollama-Instanz für die Flotte sitzt auf einem dedizierten ailab-network. Apps, die sie brauchen, treten dem Netzwerk explizit bei. Die ACL wird zu „Liegt der Service im AI-Lab-Netz?" — einfach, auditierbar, auf iptables durchsetzbar.
Auf Host-Ebene eine Default-Deny-Firewall fahren — ufw mit Regeln für SSH, 80, 443. Von außen testen, nicht aus einer Shell auf der Box. Ich habe schon Firewall-Regeln deployed, die richtig aussahen und nicht waren.
Ollama-spezifische Risiken
Ollama ist enorm nützlich und hat Sicherheits-Defaults, die aktiv gemanagt werden müssen:
Default-Bind-Adresse
Ältere Versionen banden an 0.0.0.0. OLLAMA_HOST=127.0.0.1:11434 explizit setzen. Bei Docker sicherstellen, dass der veröffentlichte Port nur auf localhost gemappt ist.
Keine Authentifizierung
Ollama selbst hat kein Auth-Modell. Authentifizierung muss in der Schicht darüber durchgesetzt werden — Ihre App stellt alle Requests, niemals der User direkt. Wer :11434 erreicht, kann jedes Modell ausführen.
Modell-Vertrauen
ollama pull aus beliebigen Registries ist eine Supply-Chain-Entscheidung. GGUF-Dateien können Metadaten enthalten, die Parser-Bugs in älteren Runtimes triggern. Auf spezifische Upstream-Tags von vertrauenswürdigen Quellen pinnen, lokal spiegeln, Checksums verifizieren.
Datenbank-Sicherheit für KI-Workloads
Vektor-Datenbanken halten etwas Sensibleres als gewöhnliche Nutzerdaten: Embeddings sämtlicher Dokumente. Mit dem richtigen Modell sind diese Embeddings teilweise reversibel. Behandeln Sie sie wie eine Kopie des Quellkorpus.
Least-Privilege-User pro App
Anwendungs-Queries niemals als Postgres-Superuser laufen lassen. Jede App bekommt eine eigene Rolle mit Grants nur auf ihr Schema. Migrationen laufen als separate, höher privilegierte Rolle hinter manuellen Deployment-Schritten.
Row-Level Security für Multi-Tenant-Retrieval
Multi-Tenant-RAG ist der Use Case, in dem Row-Level Security ihre Existenzberechtigung beweist. Die Tenant-ID wird als Session-Variable gesetzt; eine Postgres-RLS-Policy filtert jedes SELECT gegen die Embeddings-Tabelle. Eine vergessene WHERE-Klausel im Anwendungscode kann keine Daten mehr leaken.
Verschlüsselung at-rest ist Aufgabe der Host-Disk
Full-Disk-Encryption auf dem Host (LUKS) deckt die Datenbank-Files ab. Postgres-eigene transparente Verschlüsselung schafft Betriebsaufwand ohne nennenswerten Mehrwert, wenn das Bedrohungsmodell „Hosting-Provider-Kompromittierung" lautet — der Provider hält den Host-Key ohnehin im Speicher.
Secrets-Management ohne Vault
Die meisten selbst gehosteten KI-Flotten sind zu klein, um HashiCorp Vault zu rechtfertigen. Die Alternative: ein paar einfache Disziplinen bringen viel:
.env-Dateien außerhalb des Build-Kontexts
Secrets niemals ins Image bauen. Zur Laufzeit über env_file in Compose injizieren. .env in .dockerignore. Mit docker history verifizieren, dass nichts in einen Layer geleakt ist.
Variablen als Pflicht deklarieren
${VAR:?error message} in Compose, niemals ${VAR:-default} für Credentials. Bei fehlendem Secret muss der Container starten verweigern, nicht mit einem Default weiterlaufen.
Datenbank-Passwörter bei Vorfall rotieren, nicht nach Plan
Zeitgesteuertes Rotieren ist meist Compliance-Theater. Rotation bei realen Vorfällen (kompromittierter Laptop, geleakter Dump) ist, was zählt. Ein Runbook bereithalten, damit Rotation in Minuten möglich ist.
Logging und Audit
KI-Anwendungen erzeugen drei Log-Streams, die unterschiedlich behandelt werden müssen: HTTP-Access-Logs, Application-Logs (Entscheidungen, Fehler) und Prompt-Logs (Inputs und Outputs). Jeder ist auf andere Weise sensibel.
Correlation-IDs auf jedem Request
Am Edge eine UUID generieren, durch alle internen Calls propagieren. Wenn um 3 Uhr nachts etwas bricht, greppen Sie eine ID über alle Services und haben die ganze Geschichte.
Prompts mit PII redigieren
Volle Prompts zu loggen ist bequem, macht Ihren Log-Store aber zu einem Schatten-PII-Repository. Entweder beim Schreiben redigieren oder Prompt-Logs in einem separat kontrollierten Bereich mit demselben Zugriffsmodell wie die Quelldaten ablegen.
Log-Rotation und begrenzte Disk
Dockers Default-Logging füllt Festplatten. logging.driver: json-file, options.max-size: 10m, options.max-file: 3. Datenbank und Redis vertragen mehr (50m / 5).
Image-Hygiene und Supply Chain
Das KI-Ökosystem bewegt sich schnell und zieht viel Code. Die Supply Chain ist die höchstrisikoreiche Fläche, die die meisten Teams ignorieren:
- Base-Images auf Patch-Tags pinnen.
node:22-alpine3.21, nichtnode:latest. Monatlicher Rebuild, der neue Patches einsammelt. - Multi-Stage-Builds. Build-Tools (gcc, native Deps, npm mit devDependencies) leben in einer Builder-Stage. Der Runner ist ein minimales Layer mit nur dem Produktiv-Artefakt.
- Vulnerability-Scanner ausführen. Trivy oder Grype auf jedem gebauten Image, Build bei CRITICAL fehlschlagen lassen. HIGH wöchentlich triagen. Das Signal-zu-Rausch-Verhältnis bei KI-bezogenen CVEs ist schwach; Kontextentscheidungen sind nötig.
- Lockfiles sind Pflicht.
npm ci, nichtnpm install.poetry install --no-update, nichtpip install -r. Reproduzierbare Builds sind eine Sicherheitskontrolle.
Was ich nicht tue (und warum)
Ehrliche Liste — verbreitete Empfehlungen, die ich auf dieser Skala bewusst überspringe, mit Begründung:
Runtime-Exploit-Prevention-Agents (Falco, Sysdig)
Ausgezeichnete Tools auf Skala; der Betriebsaufwand und das False-Positive-Tuning sind für eine Flotte unter 100 Containern nicht zu rechtfertigen. Die obige Container-Härtung schließt denselben Großteil der Fläche.
Service Mesh mit mTLS zwischen Containern
Netzwerk-isolierte Apps mit jeweils einer Eingangstür profitieren nicht. mTLS ist die richtige Antwort, wenn lateraler Service-zu-Service-Verkehr existiert, der nicht physisch isolierbar ist.
Confidential Computing / TEEs
Sinnvoll, wenn das Bedrohungsmodell „der Hosting-Provider selbst ist ein Angreifer" lautet. Für die meisten EU-Self-Hosting-Setups auf Hetzner / OVH leistet das der rechtliche Rahmen, und TEE erhöht Komplexität ohne entsprechenden Mehrwert.
Die Reihenfolge der Umsetzung
Wer einen selbst gehosteten KI-Stack neu aufzieht und die Sicherheitsarbeit sequenzieren muss, kann diese Reihenfolge nehmen — jeder Schritt ist eigenständig wertvoll und blockiert nicht den nächsten:
- Netzwerk-Isolation: pro App ein Netzwerk, Ports an localhost gebunden, Host-Firewall Default Deny.
- Container-Härtungs-Defaults auf jedem Service.
- Non-Root-User und read-only-Dateisystem auf jedem App-Container.
- Least-Privilege-Datenbank-User; Row-Level Security bei Multi-Tenant.
- Secrets aus Images heraushalten; Variablen in Compose als Pflicht deklarieren.
- Gepinnte Base-Images; Lockfile-only-Installs; Trivy in CI.
- Correlation-IDs und begrenzte Log-Volumes.
- Optional: Vault, Mesh, Runtime-EDR — nur wenn die Skala es rechtfertigt.
Fazit
KI-Sicherheit ist keine eigene Disziplin neben Infrastruktur-Sicherheit — es ist dieselbe Disziplin, angewendet auf einen Stack mit neuen Komponenten und ein paar neuen Fehlermodi. Wenn Container-, Netzwerk- und Datenbank-Hygiene stimmen, steht die KI-spezifische Arbeit — Prompt-Injection-Abwehr, RAG-Zugriffskontrolle, Output-Filterung — auf solidem Fundament. Wenn das Fundament schwach ist, kompensiert kein Guardrail-Prompting.
Verwandt: RAG-Pipelines gegen Prompt Injection absichern, produktive Docker-Patterns für KI-Anwendungen, und selbst gehostete KI vs. Cloud-APIs.