Volle Kontrolle über LLMs: Mein Setup mit Ollama, LiteLLM und Claude Code
Wie ich lokale und Cloud-LLMs mit voller Kontrolle über Kosten und Modelle nutze - vom Gaming-PC bis Kubernetes. Ein DevOps-Ansatz für DSGVO-konforme KI-Infrastruktur.
Volle Kontrolle über LLMs: Mein Setup mit Ollama, LiteLLM und Claude Code
Als DevOps Engineer war es mir wichtig, volle Kontrolle über meine LLM-Nutzung zu haben - sowohl bei den Modellen als auch bei den Kosten. In diesem Artikel zeige ich, wie ich lokale und Cloud-LLMs kombiniere und dabei ein professionelles Setup mit Cost Tracking aufbaue.
Hardware: RTX 3090 als LLM-Powerhouse
Mein Setup basiert auf einer NVIDIA RTX 3090 mit 24GB VRAM. Das ist genug für:
- Kleinere Llama-Modelle (z.B. Llama 3.1 8B)
- Mistral-Modelle
- Fokus auf europäische LLMs (wichtig für DSGVO-Compliance!)
Ollama: Lokale LLMs auf der Gaming-GPU
Ollama läuft direkt auf meinem Gaming-PC und managed die lokalen Modelle.
Docker Compose Setup (Lokal)
version: '3.8'
services:
ollama:
image: ollama/ollama:latest
container_name: ollama
ports:
- "11434:11434"
volumes:
- ollama-data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
environment:
- NVIDIA_VISIBLE_DEVICES=0
- OLLAMA_HOST=0.0.0.0:11434
restart: unless-stopped
volumes:
ollama-data:
driver: local
Kubernetes Deployment
Für Production läuft Ollama in meinem On-Premises Kubernetes-Cluster:
apiVersion: apps/v1
kind: Deployment
metadata:
name: ollama
namespace: ai-services
spec:
replicas: 1
selector:
matchLabels:
app: ollama
template:
metadata:
labels:
app: ollama
spec:
containers:
- name: ollama
image: ollama/ollama:latest
ports:
- containerPort: 11434
name: http
env:
- name: OLLAMA_HOST
value: "0.0.0.0:11434"
resources:
requests:
nvidia.com/gpu: 1
memory: "8Gi"
cpu: "2"
limits:
nvidia.com/gpu: 1
memory: "16Gi"
cpu: "4"
volumeMounts:
- name: ollama-models
mountPath: /root/.ollama
nodeSelector:
nvidia.com/gpu.present: "true"
volumes:
- name: ollama-models
persistentVolumeClaim:
claimName: ollama-models-pvc
---
apiVersion: v1
kind: Service
metadata:
name: ollama
namespace: ai-services
spec:
selector:
app: ollama
ports:
- port: 11434
targetPort: 11434
name: http
type: ClusterIP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ollama-models-pvc
namespace: ai-services
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: local-path
Modelle laden
# Lokal
docker exec -it ollama ollama pull llama3.1:8b
docker exec -it ollama ollama pull mistral:7b
# Kubernetes
kubectl exec -it deployment/ollama -n ai-services -- ollama pull llama3.1:8b
kubectl exec -it deployment/ollama -n ai-services -- ollama pull mistral:7b
LiteLLM: Der zentrale Proxy für alle Modelle
LiteLLM ist das Herzstück meines Setups. Es bietet:
- Einheitliche API für alle LLM-Provider
- Cost Tracking
- Rate Limiting
- Load Balancing
- Model Fallbacks
Docker Compose Setup (Lokal)
version: '3.8'
services:
litellm:
image: ghcr.io/berriai/litellm:main-latest
container_name: litellm
ports:
- "4000:4000"
volumes:
- ./litellm-config.yaml:/app/config.yaml
- litellm-db:/app/database
environment:
- DATABASE_URL=sqlite:////app/database/litellm.db
- LITELLM_MASTER_KEY=${LITELLM_MASTER_KEY}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
command: --config /app/config.yaml --detailed_debug
restart: unless-stopped
depends_on:
- ollama
volumes:
litellm-db:
driver: local
LiteLLM Konfiguration
# litellm-config.yaml
model_list:
# Ollama Models (lokal)
- model_name: llama3.1
litellm_params:
model: ollama/llama3.1:8b
api_base: http://ollama:11434
- model_name: mistral
litellm_params:
model: ollama/mistral:7b
api_base: http://ollama:11434
# Claude Models (Cloud)
- model_name: claude-sonnet
litellm_params:
model: claude-sonnet-4-20250514
api_key: os.environ/ANTHROPIC_API_KEY
- model_name: claude-opus
litellm_params:
model: claude-opus-4-20250514
api_key: os.environ/ANTHROPIC_API_KEY
# Cost Tracking aktivieren
litellm_settings:
success_callback: ["langfuse"]
database_url: "sqlite:////app/database/litellm.db"
store_model_in_db: true
# Rate Limiting
router_settings:
routing_strategy: least-busy
num_retries: 3
timeout: 600
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
database_url: "sqlite:////app/database/litellm.db"
Kubernetes Deployment für LiteLLM
apiVersion: v1
kind: ConfigMap
metadata:
name: litellm-config
namespace: ai-services
data:
config.yaml: |
model_list:
- model_name: llama3.1
litellm_params:
model: ollama/llama3.1:8b
api_base: http://ollama.ai-services.svc.cluster.local:11434
- model_name: mistral
litellm_params:
model: ollama/mistral:7b
api_base: http://ollama.ai-services.svc.cluster.local:11434
- model_name: claude-sonnet
litellm_params:
model: claude-sonnet-4-20250514
api_key: os.environ/ANTHROPIC_API_KEY
litellm_settings:
success_callback: ["langfuse"]
database_url: "postgresql://litellm:password@postgres:5432/litellm"
store_model_in_db: true
general_settings:
master_key: os.environ/LITELLM_MASTER_KEY
database_url: "postgresql://litellm:password@postgres:5432/litellm"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: litellm
namespace: ai-services
spec:
replicas: 2
selector:
matchLabels:
app: litellm
template:
metadata:
labels:
app: litellm
spec:
containers:
- name: litellm
image: ghcr.io/berriai/litellm:main-latest
ports:
- containerPort: 4000
name: http
env:
- name: LITELLM_MASTER_KEY
valueFrom:
secretKeyRef:
name: litellm-secrets
key: master-key
- name: ANTHROPIC_API_KEY
valueFrom:
secretKeyRef:
name: litellm-secrets
key: anthropic-api-key
- name: DATABASE_URL
value: "postgresql://litellm:[email protected]:5432/litellm"
volumeMounts:
- name: config
mountPath: /app/config.yaml
subPath: config.yaml
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
livenessProbe:
httpGet:
path: /health
port: 4000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 4000
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: config
configMap:
name: litellm-config
---
apiVersion: v1
kind: Service
metadata:
name: litellm
namespace: ai-services
spec:
selector:
app: litellm
ports:
- port: 4000
targetPort: 4000
name: http
type: ClusterIP
---
apiVersion: v1
kind: Secret
metadata:
name: litellm-secrets
namespace: ai-services
type: Opaque
stringData:
master-key: "sk-your-master-key-here"
anthropic-api-key: "sk-ant-your-key-here"
LiteLLM Ingress (optional)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: litellm-ingress
namespace: ai-services
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
ingressClassName: nginx
tls:
- hosts:
- llm.yourdomain.com
secretName: litellm-tls
rules:
- host: llm.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: litellm
port:
number: 4000
Coding CLIs: Von Claude Code zu Open Code
Claude Code CLI
Claude Code war mein erster Versuch, hat aber ein Flickering-Problem, das mich gestört hat:
# Installation
npm install -g @anthropic-ai/claude-code
# Verwendung
export ANTHROPIC_API_KEY="sk-ant-..."
claude-code
Pros:
- Offizielle Anthropic Integration
- Gute Code-Completion
Cons:
- Flickering im Terminal
- Keine Persistenz
Open Code (aktuell in Verwendung)
# Installation
npm install -g opencode
# Verwendung mit LiteLLM
export OPENAI_API_BASE="http://localhost:4000"
export OPENAI_API_KEY="sk-your-litellm-key"
opencode --model claude-sonnet
Pros:
- Kein Flickering
- Funktioniert mit LiteLLM
Cons:
- Historie geht verloren bei /exit
- Nicht perfekt
pi-mono: Österreichisches OSS-Tool (als nächstes zu testen)
pi-mono ist ein vielversprechendes Tool aus Österreich:
# Installation
git clone https://github.com/badlogic/pi-mono.git
cd pi-mono
npm install
# Setup
cp .env.example .env
# Configure your LiteLLM endpoint
# Run
npm start
Warum interessant:
- Aus Österreich (wichtig für DSGVO) -Besseres Session Management
- Focus auf Developer Experience
OpenWebUI: Web-Interface für alle Modelle
OpenWebUI bietet eine ChatGPT-ähnliche Oberfläche für alle Modelle:
Docker Compose
version: '3.8'
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
ports:
- "3000:8080"
volumes:
- open-webui-data:/app/backend/data
environment:
- OLLAMA_BASE_URL=http://ollama:11434
- OPENAI_API_BASE_URL=http://litellm:4000/v1
- OPENAI_API_KEY=sk-your-litellm-key
restart: unless-stopped
depends_on:
- ollama
- litellm
volumes:
open-webui-data:
driver: local
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: open-webui
namespace: ai-services
spec:
replicas: 1
selector:
matchLabels:
app: open-webui
template:
metadata:
labels:
app: open-webui
spec:
containers:
- name: open-webui
image: ghcr.io/open-webui/open-webui:main
ports:
- containerPort: 8080
name: http
env:
- name: OLLAMA_BASE_URL
value: "http://ollama.ai-services.svc.cluster.local:11434"
- name: OPENAI_API_BASE_URL
value: "http://litellm.ai-services.svc.cluster.local:4000/v1"
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: litellm-secrets
key: master-key
volumeMounts:
- name: data
mountPath: /app/backend/data
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
volumes:
- name: data
persistentVolumeClaim:
claimName: open-webui-pvc
---
apiVersion: v1
kind: Service
metadata:
name: open-webui
namespace: ai-services
spec:
selector:
app: open-webui
ports:
- port: 8080
targetPort: 8080
name: http
type: ClusterIP
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: open-webui-pvc
namespace: ai-services
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path
Cost Tracking mit LiteLLM
LiteLLM trackt automatisch alle API-Calls:
# Lokale Datenbank abfragen
sqlite3 /path/to/litellm.db
# Kosten pro Modell
SELECT
model,
COUNT(*) as calls,
SUM(response_cost) as total_cost,
AVG(response_cost) as avg_cost
FROM litellm_logs
WHERE created_at > datetime('now', '-30 days')
GROUP BY model;
# Top User nach Kosten
SELECT
user,
COUNT(*) as calls,
SUM(response_cost) as total_cost
FROM litellm_logs
WHERE created_at > datetime('now', '-30 days')
GROUP BY user
ORDER BY total_cost DESC
LIMIT 10;
Kubernetes: Complete Stack Deployment
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: ai-services
---
# Complete deployment script
apiVersion: v1
kind: ConfigMap
metadata:
name: deployment-script
namespace: ai-services
data:
deploy.sh: |
#!/bin/bash
set -e
echo "🚀 Deploying AI Services Stack..."
# Apply in correct order
kubectl apply -f namespace.yaml
kubectl apply -f secrets.yaml
kubectl apply -f postgres.yaml
kubectl wait --for=condition=ready pod -l app=postgres -n ai-services --timeout=120s
kubectl apply -f ollama-deployment.yaml
kubectl wait --for=condition=ready pod -l app=ollama -n ai-services --timeout=120s
kubectl apply -f litellm-deployment.yaml
kubectl wait --for=condition=ready pod -l app=litellm -n ai-services --timeout=120s
kubectl apply -f open-webui-deployment.yaml
kubectl wait --for=condition=ready pod -l app=open-webui -n ai-services --timeout=120s
echo "Deployment complete!"
echo ""
echo "Access your services:"
echo "LiteLLM: http://$(kubectl get svc litellm -n ai-services -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):4000"
echo "OpenWebUI: http://$(kubectl get svc open-webui -n ai-services -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8080"
Monitoring Setup
# prometheus-servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: litellm-monitor
namespace: ai-services
spec:
selector:
matchLabels:
app: litellm
endpoints:
- port: http
path: /metrics
interval: 30s
Kosten-Vergleich
| Setup | Monatliche Kosten | Pros | Cons |
|---|---|---|---|
| Pure Cloud (Claude Opus) | ~€150-300 | Keine Hardware, Sofort verfügbar | Hohe laufende Kosten, Vendor Lock-in |
| Hybrid (Mein Setup) | ~€30-50 | Flexibilität, Cost Control | Initiale Hardware-Kosten |
| Pure Self-Hosted | ~€20-30 | Niedrigste Kosten | Limitiert auf kleinere Modelle |
Annahme: 1000 API-Calls/Monat mit gemischter Nutzung
Nächste Schritte
- pi-mono testen - Besseres Session Management
- Langfuse Integration - Erweiterte Analytics
- Model Fine-Tuning - Eigene Modelle auf Ollama
- Auto-Scaling - Kubernetes HPA für LiteLLM
Fazit
Die RTX 3090 zahlt sich nach ~6 Monaten gegenüber reiner Cloud-Nutzung aus, und ich habe die Freiheit, jederzeit neue Modelle zu testen - besonders wichtig für europäische LLMs!
Resources
Fragen oder Anregungen? Feel free to reach out!