· DevOps · 5 minuten Lesezeit
Docker Compose Override-Pattern: Von lokaler Entwicklung zu Azure in einer Umgebungsvariable
Die meisten Projekte haben ein docker-compose.yml für lokal und ein völlig anderes für die Cloud. Das führt zu Drift, Fehlern und "works on my machine"-Problemen. Ich zeige, wie das Docker Compose Override-Pattern beide Welten mit denselben Basis-Dateien verwaltet.
Inhalt
- Das Problem mit zwei docker-compose-Dateien
- Die Basis: docker-compose.yml
- Der lokale Override: docker-compose.override.yml
- Der Produktions-Override: docker-compose.prod.yml
- Starten der Umgebungen
- Der Weg zu Azure
- Was das Muster über Softwareentwicklung aussagt
- Die Kostenfrage: Lokal vs. Cloud für KI
Das Problem mit zwei docker-compose-Dateien
Jeder kennt das Szenario: docker-compose.yml für lokal, docker-compose.prod.yml für die Cloud, und ab einem gewissen Punkt weiß niemand mehr genau, was sich wo unterscheidet.
Services, die lokal laufen, fehlen in der Produktion. Environment Variables, die in der Produktion gesetzt sind, fehlen lokal. Gesundheitschecks, Volumes, Netzwerke – alles beginnt auseinanderzudriften.
Das Docker Compose Override-Pattern löst dieses Problem durch eine klare Hierarchie:
docker-compose.yml ← Basis: alles was immer gilt
docker-compose.override.yml ← Lokal: wird automatisch gemergt (kein Flag nötig)
docker-compose.prod.yml ← Produktion: explizit mit -f angegebenDas Ergebnis: Eine einzige Wahrheitsquelle für die Basis-Konfiguration, umgebungsspezifische Abweichungen in dedizierten Override-Dateien.
Die Basis: docker-compose.yml
services:
qdrant:
image: qdrant/qdrant:latest
ports:
- "6333:6333"
volumes:
- qdrant_storage:/qdrant/storage
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:6333/healthz"]
interval: 10s
timeout: 5s
retries: 5
backend:
build: ./backend
ports:
- "3000:3000"
environment:
- QDRANT_URL=http://qdrant:6333
- PROVIDER=${PROVIDER:-ollama}
- OLLAMA_URL=http://ollama:11434
- VECTOR_SIZE=768
- COLLECTION_NAME=instagram_memory
depends_on:
qdrant:
condition: service_healthy
frontend:
build: ./frontend
ports:
- "5173:80"
depends_on:
- backend
volumes:
qdrant_storage:Diese Datei ist umgebungsagnostisch. Sie definiert die Services, ihre Abhängigkeiten und ihre Ports. Was sie nicht definiert: welcher AI-Provider genutzt wird und ob Ollama dabei ist.
Der ${PROVIDER:-ollama} Syntax ist ein Docker-Compose-Default: Falls die Umgebungsvariable nicht gesetzt ist, wird ollama verwendet. Das ist ein sicherer Default für lokale Entwicklung.
Der lokale Override: docker-compose.override.yml
Docker Compose lädt docker-compose.override.yml automatisch, wenn man docker-compose up ohne weitere Flags aufruft. Kein -f-Flag nötig.
# Wird automatisch für lokale Entwicklung geladen
services:
ollama:
image: ollama/ollama:latest
volumes:
- ollama_data:/root/.ollama
# Kein Host-Port-Mapping: Backend → Ollama läuft intern über Docker-Netzwerk.
# Aktivieren, wenn Ollama auch vom Host aus erreichbar sein soll
# UND kein lokaler Ollama-Dienst auf Port 11434 läuft:
# ports:
# - "11434:11434"
# GPU-Unterstützung (optional, auskommentieren wenn vorhanden):
# deploy:
# resources:
# reservations:
# devices:
# - driver: nvidia
# count: 1
# capabilities: [gpu]
backend:
environment:
- PROVIDER=ollama
- OLLAMA_URL=http://ollama:11434
depends_on:
- ollama
volumes:
ollama_data:Was dieser Override tut:
- Fügt den Ollama-Service hinzu – läuft als Container, kein lokales Install nötig
- Überschreibt
PROVIDER=ollamaim Backend explizit - Erstellt ein persistentes Volume
ollama_data– einmal heruntergeladene Modelle bleiben erhalten - Stellt GPU-Support bereit (auskommentiert, für alle die NVIDIA haben)
- Kein Host-Port-Mapping für Ollama – das Backend erreicht Ollama intern via
http://ollama:11434. Ein Host-Port-Mapping würde mit einem bereits laufenden lokalen Ollama-Dienst (systemd) kollidieren (address already in use). Das Mapping ist auskommentiert und kann bei Bedarf aktiviert werden.
Das Merging durch Docker Compose ist intelligent: Services aus der Override-Datei werden mit den Basis-Services zusammengeführt. Der backend-Service aus dem Override erweitert den backend-Service aus der Basis – er ersetzt ihn nicht.
Der Produktions-Override: docker-compose.prod.yml
# Explizit mit -f geladen für Produktion
services:
backend:
environment:
- PROVIDER=openai
- OPENAI_API_KEY=${OPENAI_API_KEY}
# Kein OLLAMA_URL – OpenAI braucht das nicht
# Kein Ollama-Service – spart Ressourcen und Kosten in der CloudDieser Override:
- Setzt
PROVIDER=openai– der Backend startet mit dem OpenAI-Provider - Injiziert
OPENAI_API_KEYaus der Shell-Umgebung - Enthält keinen Ollama-Service – in der Cloud zahlt man für Compute, also kein Container der nicht gebraucht wird
Starten der Umgebungen
# Lokal (automatisches Override) — Modelle werden beim ersten Start automatisch heruntergeladen:
docker-compose up --build -d
# Produktion (explizites Override):
export OPENAI_API_KEY=sk-...
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -dBeim ersten lokalen Start lädt der Ollama-Container automatisch nomic-embed-text und llama3.2 via ollama/entrypoint.sh. Kein manueller docker exec-Schritt notwendig.
Der Weg zu Azure
Wenn dieses System auf Azure deployed wird, bieten sich mehrere Optionen an:
Option 1: Azure Container Apps (empfohlen)
Azure Container Apps unterstützt Docker Compose nativ über das containerapp compose CLI. Das bedeutet: Die bestehenden Compose-Dateien können direkt verwendet werden.
# Azure Container Apps Deployment
az containerapp compose create \
--resource-group myRG \
--environment myEnv \
--compose-file-path docker-compose.yml \
--compose-file-path docker-compose.prod.ymlQdrant wird als Container-App deployed, Backend als Container-App, Frontend als statische Web-App oder Container-App. Keine Kubernetes-Komplexität, automatisches Scaling, Managed Certificates.
Option 2: Azure Container Instances (für einfachere Setups)
Für kleinere Deployments ohne automatisches Scaling eignen sich Azure Container Instances mit einer YAML-Deployment-Definition, die aus den Compose-Dateien abgeleitet wird.
Option 3: AKS (für Enterprise)
Wenn Kubernetes benötigt wird (z.B. für Enterprise-Skalierung oder bestehende AKS-Cluster), lassen sich die Compose-Dateien mit kompose convert in Kubernetes-Manifeste überführen. Die Provider-Abstraktion bleibt identisch – nur die Deployment-Plattform ändert sich.
Was das Muster über Softwareentwicklung aussagt
Das Override-Pattern ist keine Docker-spezifische Technik. Es ist eine Anwendung desselben Prinzips, das ich überall in guten Systemen sehe:
Separation of Concerns durch Layering.
Eine Basis, die immer gilt. Schichten darüber, die umgebungsspezifisches Verhalten hinzufügen, ohne die Basis zu ändern. Jede Schicht ist klein, lesbar, verständlich.
In der Praxis bedeutet das:
- Ein Entwickler, der neu ins Projekt kommt, führt
docker-compose upaus. Es funktioniert. - Ein DevOps-Engineer, der die Produktion deployed, verwendet dieselbe Basis-Datei plus ein Override. Kein Hidden State, keine Geheimnisse.
- Wenn sich die Produktion ändert (z.B. Wechsel von OpenAI zu Azure OpenAI), ändert man das Override. Die Basis bleibt unberührt.
Die Kostenfrage: Lokal vs. Cloud für KI
Das war eine der ersten Fragen, die ich mir gestellt habe: Soll ich Ollama lokal oder in der Cloud hosten?
Die ehrliche Antwort hängt vom Anwendungsfall ab:
| Faktor | Ollama lokal/Docker | OpenAI Cloud |
|---|---|---|
| Kosten bei niedrigem Volumen | ✅ Kostenlos | ✅ Minimal (~$0.02/1000 Anfragen) |
| Kosten bei hohem Volumen | ✅ Fix (Hardware) | ⚠️ Skaliert mit Nutzung |
| Datenschutz | ✅ Alles lokal | ⚠️ Daten gehen zu OpenAI |
| Verfügbarkeit | ⚠️ Eigene Infra | ✅ 99.9% SLA |
| Wartungsaufwand | ⚠️ Modell-Updates | ✅ Managed |
| Performance (Cold Start) | ⚠️ Langsam | ✅ Schnell |
Für Proof-of-Concepts und Entwicklung: Ollama. Für produktive Systeme mit vielen Nutzern: OpenAI oder Azure OpenAI. Für Systeme mit strengen Datenschutzanforderungen: Ollama auf eigenem Server oder Azure OpenAI in einer private Deployment-Konfiguration.
Das Override-Pattern macht diesen Wechsel zu einem operativen, nicht architektonischen Entscheid. Das ist der Punkt.
Nächster Artikel: Warum "module": "Node16" deine Vite-Extension-Build-Pipeline zerstört – und wie du es fixst