Neu veröffentlicht: E-Commerce mit Power Pages, Stripe & Analytics

· 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

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 angegeben

Das 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:

  1. Fügt den Ollama-Service hinzu – läuft als Container, kein lokales Install nötig
  2. Überschreibt PROVIDER=ollama im Backend explizit
  3. Erstellt ein persistentes Volume ollama_data – einmal heruntergeladene Modelle bleiben erhalten
  4. Stellt GPU-Support bereit (auskommentiert, für alle die NVIDIA haben)
  5. 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 Cloud

Dieser Override:

  1. Setzt PROVIDER=openai – der Backend startet mit dem OpenAI-Provider
  2. Injiziert OPENAI_API_KEY aus der Shell-Umgebung
  3. 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 -d

Beim 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.yml

Qdrant 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 up aus. 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:

FaktorOllama lokal/DockerOpenAI 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

Zurück zum Blog

Ähnliche Beiträge

Alle Beiträge ansehen