Leitfaden
On-Premise-LLM in der Praxis: Ein schlankes Setup mit Rocky Linux und Docker Compose
Vom blanken Server zum produktiven, DSGVO-konformen LLM-Dienst für 400 Nutzer – Schritt für Schritt mit Rocky Linux 9, NVIDIA Blackwell, vLLM, LiteLLM und Open WebUI. Inklusive vollständiger docker-compose.yml.
On-Premise-LLM in der Praxis: Ein schlankes Setup mit Rocky Linux und Docker Compose
Stand: Juni 2026. Treiber-, CUDA- und vLLM-Versionen für die Blackwell-Generation bewegen sich schnell. Prüfen Sie die konkreten Versionsstände vor dem produktiven Rollout. Die Konfigurationen sind als Referenz gedacht, nicht als Copy-Paste-Garantie.
Im vorherigen Beitrag lautete das Fazit: Bei sensiblen Datenklassen ist Self-Hosting die einzige Architektur mit echter Datensouveränität – frei von US-CLOUD-Act-Exposition. Die Gegenfrage, die danach immer kommt: „Klingt teuer und kompliziert im Betrieb.”
Muss es nicht sein. Wer keine Hyperscaler-Ambitionen hat, sondern schlicht 400 Mitarbeiter mit einem internen Assistenten versorgen will, braucht kein Kubernetes-Cluster und kein dreiköpfiges Plattform-Team. Ein einzelner gut dimensionierter Server, Rocky Linux 9 und Docker Compose reichen vollständig aus. Dieser Beitrag zeigt den Weg vom blanken Blech bis zum produktiven Dienst.
Das Zielbild
Wir bauen einen Stack aus vier Kernkomponenten plus Infrastruktur, alle als Container auf einem Host:
- vLLM – die Inferenz-Engine. Bedient das Modell (Qwen3-32B) mit Continuous Batching und liefert ein OpenAI-kompatibles API.
- LiteLLM – das Gateway. Einheitliches API, virtuelle Schlüssel pro Team, Budgets, Rate-Limits und zentrales Audit-Logging.
- Open WebUI – das Frontend. Chat-Oberfläche mit OIDC-Anmeldung und Rollenverwaltung.
- PostgreSQL – Persistenz für LiteLLM (Schlüssel, Verbrauch) und Open WebUI.
- Caddy – Reverse Proxy mit automatischem TLS.
Der Datenfluss ist streng nach innen gerichtet: Nutzer erreichen nur Open WebUI über Caddy. Open WebUI spricht mit LiteLLM, LiteLLM mit vLLM – beide nur im internen Docker-Netz, nie nach außen exponiert.
Als Hardware-Grundlage dient der im letzten Beitrag dimensionierte Server: 1× NVIDIA RTX PRO 6000 Blackwell (96 GB) in einem Server mit reichlich RAM (256 GB), NVMe-Speicher für die Modellgewichte und einem Netzteil, das die 600 W der Karte plus System trägt. Für den Serverraum empfiehlt sich die Server- oder Max-Q-Edition der Karte (Blower-Kühlung, geringere Leistungsaufnahme).
Schritt 1: Rocky Linux 9 vorbereiten
Eine Minimalinstallation von Rocky Linux 9 genügt. Nach der Erstinstallation zunächst aktualisieren und Basiswerkzeuge installieren:
sudo dnf update -y
sudo dnf install -y epel-release dnf-plugins-core curl tar
sudo reboot
Zwei Rocky-Eigenheiten, die man kennen muss:
SELinux läuft standardmäßig im Enforcing-Modus. Das ist gut und sollte so bleiben – wir berücksichtigen es bei den Volume-Mounts (:z/:Z). Schalten Sie SELinux nicht pauschal ab; das ist genau die Art von Härtung, die man bei einem DSGVO-relevanten Dienst behalten will.
firewalld ist der Standard-Paketfilter. Wir öffnen später nur 80/443 und halten alle internen Dienste im Docker-Netz:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Schritt 2: NVIDIA-Treiber für Blackwell installieren
Hier liegt der versionsabhängigste Teil. Wichtig: Blackwell-GPUs benötigen die Open-Kernel-Module (nvidia-open) – die proprietären Module unterstützen die Architektur nicht. Das ist seit der R560-Treiberserie ohnehin NVIDIAs Standardweg.
Rocky 9 nutzt DNF-Module-Streams (anders als Rocky 10, das reguläre Pakete verwendet). Zunächst Build-Voraussetzungen und das CUDA-Repository:
sudo dnf install -y kernel-devel-$(uname -r) kernel-headers-$(uname -r) \
gcc make dkms acpid libglvnd-glx libglvnd-opengl libglvnd-devel pkgconf
sudo dnf config-manager --add-repo \
https://developer.download.nvidia.com/compute/cuda/repos/rhel9/x86_64/cuda-rhel9.repo
sudo dnf clean all && sudo dnf -y makecache
Dann die Open-Variante des Treibers mit DKMS (rebaut sich automatisch nach Kernel-Updates):
sudo dnf module install -y nvidia-driver:open-dkms
# Falls der Modulstream Probleme macht, alternativ:
# sudo dnf install -y nvidia-open --allowerasing
sudo reboot
Nach dem Neustart prüfen – die Karte muss mit 96 GB auftauchen:
nvidia-smi
Zeigt der Befehl Ihre RTX PRO 6000, ist die Basis fertig. Erscheint stattdessen noch nouveau, wurde der Open-Source-Stock-Treiber nicht verdrängt; dann Nouveau explizit blacklisten und neu bauen.
Schritt 3: Docker und NVIDIA Container Toolkit
Rocky bringt von Haus aus Podman mit; wir wollen aber Docker Compose, also Docker CE aus dem offiziellen Repo:
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker
Damit die Container die GPU sehen, das NVIDIA Container Toolkit installieren und die Docker-Runtime konfigurieren:
curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo \
| sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo
sudo dnf install -y nvidia-container-toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
Der entscheidende Funktionstest – nvidia-smi innerhalb eines Containers:
sudo docker run --rm --gpus all nvidia/cuda:12.8.0-base-ubuntu24.04 nvidia-smi
Sehen Sie hier dieselbe Ausgabe wie auf dem Host, ist die GPU-Durchreichung komplett. Schlägt es mit „permission denied” fehl, ist meist SELinux die Ursache – nvidia-ctk setzt die nötigen Labels, in hartnäckigen Fällen hilft --security-opt label=disable gezielt für den GPU-Container.
Schritt 4: Der Stack als docker-compose.yml
Jetzt das Herzstück. Legen Sie ein Projektverzeichnis an (/opt/llm-stack) und darin folgende Dateien.
Zuerst die .env mit den Geheimnissen – Rechte restriktiv setzen (chmod 600 .env) und niemals ins Git committen:
# .env
HF_TOKEN=hf_xxxxxxxx
VLLM_API_KEY=internal-vllm-key-aendern
LITELLM_MASTER_KEY=sk-master-aendern
PG_PASS=ein-langes-passwort
OIDC_CLIENT_ID=open-webui
OIDC_CLIENT_SECRET=xxxxxxxx
Dann die docker-compose.yml:
name: llm-stack
services:
vllm:
image: vllm/vllm-openai:latest # Tag mit Blackwell-/sm_120-Support verwenden
container_name: vllm
restart: unless-stopped
command:
- "Qwen/Qwen3-32B-FP8" # vorquantisiertes FP8-Checkpoint
- "--served-model-name=qwen3-32b"
- "--kv-cache-dtype=fp8"
- "--max-model-len=32768"
- "--gpu-memory-utilization=0.92"
- "--max-num-seqs=32"
- "--enable-prefix-caching"
- "--api-key=${VLLM_API_KEY}"
environment:
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
volumes:
- ./models:/root/.cache/huggingface:Z
ipc: host # vLLM braucht großen Shared Memory
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
networks: [internal]
postgres:
image: postgres:16
container_name: postgres
restart: unless-stopped
environment:
- POSTGRES_USER=litellm
- POSTGRES_PASSWORD=${PG_PASS}
- POSTGRES_DB=litellm
volumes:
- ./pgdata:/var/lib/postgresql/data:Z
networks: [internal]
litellm:
image: ghcr.io/berriai/litellm:main-stable
container_name: litellm
restart: unless-stopped
depends_on: [vllm, postgres]
command: ["--config=/app/config.yaml", "--port=4000"]
volumes:
- ./litellm-config.yaml:/app/config.yaml:Z
environment:
- LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY}
- DATABASE_URL=postgresql://litellm:${PG_PASS}@postgres:5432/litellm
- VLLM_API_KEY=${VLLM_API_KEY}
networks: [internal]
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
depends_on: [litellm]
environment:
- OPENAI_API_BASE_URL=http://litellm:4000/v1
- OPENAI_API_KEY=${LITELLM_MASTER_KEY}
# OIDC / SSO
- ENABLE_OAUTH_SIGNUP=true
- OAUTH_PROVIDER_NAME=Keycloak
- OPENID_PROVIDER_URL=https://id.example.org/realms/intern/.well-known/openid-configuration
- OAUTH_CLIENT_ID=${OIDC_CLIENT_ID}
- OAUTH_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
- DEFAULT_USER_ROLE=pending # neue Nutzer erst nach Admin-Freigabe aktiv
# Telemetrie aus
- ANONYMIZED_TELEMETRY=false
- DO_NOT_TRACK=true
- SCARF_NO_ANALYTICS=true
volumes:
- ./open-webui:/app/backend/data:Z
networks: [internal, web]
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
depends_on: [open-webui]
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:Z
- ./caddy-data:/data:Z
networks: [web]
networks:
internal:
web:
Die litellm-config.yaml – sie bindet das vLLM-Backend als OpenAI-kompatibles Modell ein:
model_list:
- model_name: qwen3-32b
litellm_params:
model: openai/qwen3-32b
api_base: http://vllm:8000/v1
api_key: os.environ/VLLM_API_KEY
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
database_url: os.environ/DATABASE_URL
litellm_settings:
drop_params: true
# success_callback: ["langfuse"] # aktivieren, sobald Langfuse läuft
Und die Caddyfile – ersetzt nur die Domain, TLS holt sich Caddy automatisch:
ai.example.org {
reverse_proxy open-webui:8080
}
Starten:
cd /opt/llm-stack
sudo docker compose up -d
sudo docker compose logs -f vllm
Beim ersten Start lädt vLLM die Modellgewichte (mehrere GB) herunter – das dauert. Danach steht der Dienst unter Ihrer Domain.
Schritt 5: Die Engine richtig einstellen – FP8 oder NVFP4?
Eine bewusste Designentscheidung steckt im command oben: Wir fahren das Modell in FP8, nicht in NVFP4. Der Grund ist hardwarespezifisch und ein klassischer Stolperstein.
Die RTX PRO 6000 ist „Desktop-Blackwell” (SM 12.0), nicht das Datacenter-Blackwell der B100/B200 (SM 10.0). NVIDIAs nativer NVFP4-Tensor-Core-Pfad landete zuerst auf den Datacenter-Karten; auf SM 12.0 fällt vLLM bei manchen Modellpfaden noch auf einen Kernel zurück, der FP4 intern nach BF16 entpackt – der Speichervorteil bleibt, der Rechen-Speedup geht verloren. Das holt die Software Stück für Stück nach, aber Stand Mitte 2026 ist es nichts, worauf man eine Produktivinstallation blind aufsetzt.
FP8 dagegen ist der verlässliche Pfad: native FP8-Tensor-Cores (rund 2 PFLOPS auf dieser Karte), etwa halbierter VRAM-Bedarf gegenüber BF16, breite vLLM-Unterstützung. Mit --kv-cache-dtype=fp8 legen Sie auch den KV-Cache in FP8 ab und gewinnen weiteren Spielraum für parallele Sessions.
Der praktische Effekt der 96 GB: Ein 32B-Modell passt mit komfortablem KV-Cache-Budget auf eine einzige Karte – Sie sparen sich die Tensor-Parallelism-Komplexität über zwei GPUs (zumal die RTX PRO 6000 kein NVLink mehr hat und der Austausch sonst über PCIe liefe). --max-num-seqs=32 deckt die im Vorgängerbeitrag ermittelte Spitzenlast mit Reserve ab; --enable-prefix-caching beschleunigt wiederkehrende System-Prompts und RAG-Kontexte spürbar.
NVFP4 lohnt einen Test, sobald Ihre vLLM-Version den Kernel-Pfad für Ihr Modell sauber unterstützt – Benchmarks auf genau dieser Karte zeigen für dichte 32B-Modelle dann näherungsweise doppelten Durchsatz und deutlich niedrigere Time-to-First-Token. Bis dahin: FP8 produktiv, NVFP4 auf der Testbank.
Schritt 6: Zugriff und Identität
Für 400 Nutzer ist eine zentrale Anmeldung Pflicht. Open WebUI bindet sich oben per OIDC an einen Identity Provider (im Beispiel Keycloak, das Sie ebenfalls on-prem betreiben). DEFAULT_USER_ROLE=pending sorgt dafür, dass sich niemand unkontrolliert selbst freischaltet – neue Konten muss ein Administrator aktivieren. Das ist beim kontrollierten Rollout Gold wert.
Den administrativen Zugriff auf den Server selbst (SSH, Container-Logs, Grafana) legen Sie nicht ins offene Internet, sondern hinter Tailscale. Nutzer erreichen ausschließlich die Chat-Oberfläche über Caddy; alles andere bleibt im privaten Mesh. Damit ist der Host von außen praktisch unsichtbar.
LiteLLM ist hier mehr als ein Durchlauferhitzer: Über das Gateway vergeben Sie pro Abteilung virtuelle Schlüssel mit eigenen Budgets und Rate-Limits, ohne dass Endnutzer je den echten vLLM-Schlüssel sehen. Das ist die Stelle, an der aus „ein Modell läuft” ein „mehrere Teams nutzen es kontrolliert” wird.
Schritt 7: Beobachtbarkeit (optional, aber empfohlen)
Zwei Ebenen lohnen sich:
System/GPU: Der NVIDIA DCGM-Exporter liefert GPU-Auslastung, Speicher und Temperatur an Prometheus, Grafana visualisiert. So sehen Sie, ob --max-num-seqs zu knapp oder zu großzügig steht und wann eine zweite Karte fällig wird.
LLM-Ebene: Langfuse (self-hosted) macht Anfragen, Latenzen, Token-Verbräuche und Qualität sichtbar. Hinweis zur Ehrlichkeit: Langfuse v3 bringt im Self-Hosting ClickHouse, Redis und einen Objektspeicher mit – das ist nicht mehr „simpel”. Deployen Sie es daher als eigenen Compose-Stack neben dem Kern (Langfuse stellt ein offizielles Compose bereit) und aktivieren dann den success_callback: ["langfuse"] in der LiteLLM-Konfiguration. Wer es minimal halten will, nutzt zunächst nur die in LiteLLM eingebaute Verbrauchserfassung in Postgres.
DSGVO-Härtung: das, was den Aufwand rechtfertigt
Die ganze Übung hat ein Ziel – Datensouveränität. Entsprechend konsequent konfigurieren:
- Keine Telemetrie nach außen. Open WebUI ist oben bereits stummgeschaltet. Prüfen Sie regelmäßig per
firewalld/Egress-Filter, dass kein Container ungewollt heimtelefoniert. Idealerweise erlauben Sie ausgehenden Verkehr nur für das initiale Modell-Pull und sperren ihn danach. - Verschlüsselung im Ruhezustand. Die Datenträger mit Postgres-Daten und Chatverläufen auf LUKS legen. Verliert ein Laufwerk das Gebäude, sind die Daten unlesbar.
- Aufbewahrung steuern. Definieren Sie, wie lange Chatverläufe und Logs vorgehalten werden, und automatisieren Sie die Löschung. Datenminimierung ist keine Kür.
- Audit-Trail. LiteLLM protokolliert jede Anfrage zentral – wer, welches Modell, wann. Das ist Ihre Nachweisgrundlage für die Datenschutz-Folgenabschätzung.
- SELinux an lassen. Der Enforcing-Modus ist Teil der Härtung, nicht ein Hindernis.
Tag 2: Betrieb und Skalierung
Der laufende Betrieb bleibt überschaubar:
- Modell-Updates: Neuen Image-Tag bzw. Checkpoint setzen,
docker compose up -d vllm. Dank versionierter Tags ist ein Rollback eine Zeile. - Kernel-Updates: Durch DKMS baut sich der Treiber nach Kernel-Updates selbst neu. Trotzdem nach Updates
nvidia-smiund einen Smoke-Test fahren. - Backups:
pgdataund das Open-WebUI-Volume regelmäßig sichern – das sind Ihre Nutzer- und Verlaufsdaten. Die Modellgewichte sind reproduzierbar und brauchen kein Backup. - Skalierung: Reicht eine Karte nicht mehr, ist der nächste Schritt eine zweite RTX PRO 6000 mit
--tensor-parallel-size=2(Kommunikation über PCIe Gen 5) – oder, sauberer für Hochverfügbarkeit, ein zweiter Host und Lastverteilung über LiteLLM. Weil der Engpass bei dieser Karte oft die Speicherbandbreite ist (rund ein Drittel einer HBM-Datacenter-GPU), bringt die zweite Karte real mehr als die reine TFLOPS-Rechnung vermuten lässt.
Fazit
Ein souveräner, DSGVO-konformer LLM-Dienst für 400 Nutzer ist kein Großprojekt. Ein Server, eine Blackwell-Karte, Rocky Linux und eine überschaubare docker-compose.yml genügen. Die drei Dinge, an denen es in der Praxis hängt, sind nicht exotisch: der richtige Treiberpfad (Open-Module für Blackwell), die passende Quantisierung (FP8 produktiv, NVFP4 erst nach Test) und eine konsequente Härtung (SELinux an, Egress zu, Verschlüsselung an).
Damit haben Sie genau das, wofür die ganze Souveränitätsdiskussion geführt wurde: ein System, dessen sensibelste Daten das Haus nie verlassen – und das Sie an einem Nachmittag aufsetzen können.
Hinweis: Dieser Beitrag ist eine technische Anleitung und ersetzt keine Rechts- oder Datenschutzberatung. Konkrete Versionsstände und Modell-Lizenzbedingungen bitte vor dem Produktiveinsatz verifizieren.