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

· David Göschel · DevOps  · 7 minuten Lesezeit

Docker Compose Override-Pattern für lokale Entwicklung und Azure-Deployment

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.

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          # base: always applies
docker-compose.override.yml # local: merged automatically (no -f flag needed)
docker-compose.prod.yml     # production: specified explicitly with -f

Das Ergebnis: Eine einzige Wahrheitsquelle für die Basis-Konfiguration, umgebungsspezifische Abweichungen in dedizierten Override-Dateien.

Die Basis-Konfiguration

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 lädt docker-compose.override.yml automatisch, sobald docker-compose up ohne weitere Flags aufgerufen wird. Kein -f-Flag nötig.

# automatically loaded for local development
services:
  ollama:
    image: ollama/ollama:latest
    volumes:
      - ollama_data:/root/.ollama
    # No host port mapping: backend reaches Ollama via internal Docker network.
    # Enable only if no local Ollama service is running on port 11434
    # and Ollama needs to be reachable from the host:
    # ports:
    #   - "11434:11434"
    # GPU support (optional, uncomment if available):
    # 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 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

# explicitly loaded for production with -f
services:
  backend:
    environment:
      - PROVIDER=openai
      - OPENAI_API_KEY=${OPENAI_API_KEY}
    # no OLLAMA_URL needed for OpenAI

  # no Ollama service: saves resources and cost in the cloud

Dieser Override:

  1. Setzt PROVIDER=openai – das 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

# local (automatic override): models are downloaded automatically on first start
docker-compose up --build -d

# production (explicit 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.

Diagramm des Docker Compose Override-Patterns mit drei Dateien und ihren Merge-Ebenen Die Basis-Datei gilt immer. Der lokale Override wird automatisch dazugemergt. Der Produktions-Override wird explizit angegeben. Alle drei Dateien teilen dieselbe Struktur.

Der Weg zu Azure

Wenn dieses System auf Azure deployed wird, bieten sich mehrere Optionen an:

Azure Container Apps

Azure Container Apps unterstützt Docker Compose nativ über das containerapp compose CLI. Das bedeutet: Die bestehenden Compose-Dateien können direkt verwendet werden.

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.

Azure Container Instances

Für kleinere Deployments ohne automatisches Scaling eignen sich Azure Container Instances mit einer YAML-Deployment-Definition, die aus den Compose-Dateien abgeleitet wird.

AKS

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 Überraschungen.
  • Wenn sich die Produktion ändert (z.B. Wechsel von OpenAI zu Azure OpenAI), ändert man das Override. Die Basis bleibt unberührt.

Lokal oder 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 Antwort hängt vom Anwendungsfall ab:

FaktorOllama lokal/DockerOpenAI Cloud
Kosten bei niedrigem VolumenKostenlosMinimal (~$0.02/1000 Anfragen)
Kosten bei hohem VolumenFix (Hardware)Skaliert mit Nutzung
DatenschutzAlles lokalDaten gehen zu OpenAI
VerfügbarkeitEigene Infra99.9% SLA
WartungsaufwandModell-Updates nötigManaged
Performance (Cold Start)Langsam auf CPUSchnell

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.

Alle Artikel der Serie

  1. Vision und Systemübersicht: Chrome Extension, RAG-Architektur, Projekthintergrund: Artikel lesen
  2. RAG-System Aufbau: Qdrant, Embeddings, Cosine-Ähnlichkeit in TypeScript: Artikel lesen
  3. AI Provider Abstraktion: Ollama vs. OpenAI, Interface-Design, kein Vendor-Lock-in: Artikel lesen
  4. Chrome Extension MV3: Drei isolierte Laufzeitkontexte, Message Passing, Strategy Pattern: Artikel lesen
  5. Docker Compose Strategie: Override-Pattern, von lokal zu Azure (dieser Artikel)
  6. Ollama lokal vs. Docker: Die Entscheidung und ihre Konsequenzen: Artikel lesen
  7. Ollama Auto-Pull Entrypoint: Automatisiertes Modell-Setup beim Container-Start: Artikel lesen
  8. tsconfig und Vite: Node16 vs. bundler, warum Vite eigene Regeln hat: Artikel lesen
  9. Instagram Caption mit MutationObserver vollständig laden: Artikel lesen
  10. Chrome Extension Foundation mit Health-Dot und Retry-Queue: Artikel lesen
  11. Phase 2 Features: Shadow DOM Overlay, Tailwind v4, Duplicate Detection: Artikel lesen
  12. Race Condition bei der Plattformerkennung: Wie ein UI-Event die Instagram-Erkennung bricht: Artikel lesen
  13. PostId-Extraktion in zwei Instagram-Layouts: querySelector vs. Ancestor-Traversal: Artikel lesen
  14. Instagram Karussell vollständig erfassen mit MutationObserver: Artikel lesen
  15. Notiz und Tags beim Screenshot-Speichern: Artikel lesen
  16. Instagram Tastatur-Shortcuts blockieren Chrome Extension Eingaben: Artikel lesen
  17. Lowercase-Normalisierung und Duplikat-Erkennung im Tag-Input: Artikel lesen
  18. Zitadel Login V2 in Docker Compose: drei versteckte Fehler: Artikel lesen
  19. PKCE OAuth in einer Chrome MV3 Extension: Artikel lesen
  20. React Frontend mit react-oidc-context und Zitadel: Artikel lesen
  21. Vite Build-Time-Umgebungsvariablen in Docker: Artikel lesen
  22. Event-Driven Ingestion mit BullMQ und Redis: Artikel lesen
  23. MinIO statt Azurite: S3-kompatible Objektspeicherung lokal und auf Hetzner: Artikel lesen
  24. access_token, id_token und der Userinfo-Endpoint: was wohin gehört: Artikel lesen
  25. Qdrant Multi-Tenancy: Pro Nutzer eine eigene Collection: Artikel lesen
  26. Wenn Backend und Frontend unterschiedliche Typen kennen: Artikel lesen
  27. Zitadel Bootstrap entfernt: Host-Header-Bug und manuelles Setup: Artikel lesen
  28. Backend Code Review: sechs Probleme vor dem Launch behoben: Artikel lesen
  29. Traefik statt NGINX: Reverse Proxy für einen wachsenden Docker-Compose-Stack: Artikel lesen
  30. Zweischichtiges Rate Limiting: Traefik und express-rate-limit mit Redis: Artikel lesen
  31. DSGVO Art. 17 korrekt implementieren: Promise.allSettled und Export-Batching: Artikel lesen
  32. Embedding-Modell-Lock-in: Warum mxbai-embed-large eine Produktionsentscheidung für immer ist: Artikel lesen
  33. Docker Volumes in Produktion: Named Volumes, Bind Mounts und der Hetzner-Volume-Trick: Artikel lesen
  34. Zwei Sicherheitslücken vor dem Launch: Redis ohne Auth und ein offener Qdrant-Admin-Port: Artikel lesen
  35. Traefik als einziger Einstiegspunkt im Docker Compose Stack: Artikel lesen
  36. Zitadel hinter Traefik richtig verdrahten mit Issuer, JWKS und Login V2: Artikel lesen
  37. Frontend reparieren wenn der nginx Healthcheck an localhost scheitert: Artikel lesen
  38. Observability für meinen Docker Compose Stack mit Bull Board und Dozzle: Artikel lesen
  39. Qdrant Dashboard sicher öffnen mit lokalem Traefik und SSH Tunnel: Artikel lesen
  40. Diagnose: Warum mein Chunking trotz Tokenisierung noch scheiterte: Artikel lesen
  41. Entscheidung: Warum ich den Chunk auf 1500 Tokens gesetzt habe: Artikel lesen
  42. Implementierung: Wie ich den Embedding Workflow in mehrere saubere Schritte zerlegt habe: Artikel lesen
  43. Validierung: Wie ich Chunking, Speicherung und Suche wieder zusammenbringe: Artikel lesen

Du planst eine Cloud-Deployment-Strategie für ein KI-System und willst Entwicklung und Produktion sauber trennen? Lass uns das gemeinsam einschätzen.

Zurück zum Blog

Ähnliche Beiträge

Alle Beiträge ansehen