extends, include und mehrere -f-Flags im Vergleich
Sobald ein Projekt mehrere Umgebungen oder mehrere verwandte Services hat, wird eine einzelne, monolithische compose.yaml schnell unuebersichtlich. extends und das neuere include bieten zwei unterschiedliche Wege, gemeinsame Definitionen auszulagern, ohne staendig alles zu kopieren.
Inhaltsverzeichnis
- 1. Warum eine einzige Compose-Datei irgendwann nicht mehr reicht
- 2. extends: einzelne Service-Definitionen vererben
- 3. Grenzen von extends: was nicht funktioniert
- 4. include: ganze Compose-Dateien als Projektbausteine
- 5. include versus mehrere -f-Flags: der praktische Unterschied
- 6. extends und include kombinieren
- 7. Gemeinsame Netzwerke und Volumes ueber Dateigrenzen hinweg
- 8. Praxisbeispiel: modulare Struktur in der CI-Pipeline nutzen
- 9. Entscheidungshilfe: extends, include oder mehrere -f-Dateien
- 10. Zusammenfassung
- 11. FAQ
1. Warum eine einzige Compose-Datei irgendwann nicht mehr reicht
Ein typisches Projekt beginnt mit einer einzigen compose.yaml, die App, Datenbank und Cache definiert. Mit wachsender Komplexitaet kommen aber schnell Variationen hinzu: eine Testumgebung ohne Mailhog, eine Staging-Umgebung mit anderen Ressourcenlimits, ein CI-Setup ohne Volumes fuer schnellere Builds. Kopiert man die Datei fuer jeden Fall, driften die Kopien innerhalb weniger Wochen auseinander, weil niemand zuverlaessig alle Kopien synchron haelt.
Compose bietet dafuer zwei komplementaere Mechanismen: extends erlaubt die Wiederverwendung einzelner Service-Definitionen ueber Dateigrenzen hinweg, waehrend include ganze Compose-Dateien als eigenstaendige, aber zusammengehoerige Projektteile einbindet. Beide loesen unterschiedliche Probleme und lassen sich sogar kombinieren, sollten aber nicht wahllos vermischt werden, sonst wird die Struktur selbst zum Debugging-Problem.
2. extends: einzelne Service-Definitionen vererben
Mit extends kann ein Service die Konfiguration eines anderen Service als Basis uebernehmen, entweder aus derselben Datei oder aus einer anderen. Das Zielszenario ist typischerweise eine Basisdefinition mit Image, Netzwerk und gemeinsamen Umgebungsvariablen, von der mehrere konkrete Services erben und nur die Unterschiede ergaenzen. Anders als bei einem einfachen YAML-Anchor funktioniert extends service-uebergreifend und sogar dateiuebergreifend, was reine YAML-Anker nicht koennen.
Wichtig ist die Merge-Semantik: Listen wie ports oder volumes werden zusammengefuehrt, nicht ersetzt, waehrend Skalarwerte wie image vom erbenden Service ueberschrieben werden koennen. Diese Feinheiten sind der Hauptgrund, warum extends primaer fuer wirklich gemeinsame Basiskonfiguration taugt und nicht fuer Services, die sich fundamental unterscheiden, dort fuehrt es eher zu Verwirrung als zu Einsparung.
# common.yaml
services:
app-base:
image: myapp:latest
environment:
NODE_ENV: production
networks:
- backend
# compose.yaml
services:
web:
extends:
file: common.yaml
service: app-base
ports:
- "3000:3000"
worker:
extends:
file: common.yaml
service: app-base
command: ["node", "worker.js"]
3. Grenzen von extends: was nicht funktioniert
Ein oft uebersehenes Detail: extends unterstuetzt weder depends_on noch links noch volumes_from aus der Basisdefinition, weil diese Beziehungen zu anderen Services referenzieren, die im Kontext der Basisdatei moeglicherweise gar nicht existieren. Wer versucht, eine Datenbankabhaengigkeit ueber extends zu vererben, muss depends_on im erbenden Service manuell erneut angeben, was leicht vergessen wird und zu Race Conditions beim Start fuehrt.
Ausserdem funktioniert extends nur einstufig zuverlaessig in der Praxis: Ein Service, der von einem bereits erbenden Service weitererbt, ist zwar technisch moeglich, macht die Nachvollziehbarkeit aber schnell zunichte, weil man mehrere Dateien gleichzeitig offen haben muss, um die effektive Konfiguration zu verstehen. Der Befehl docker compose config ist deshalb bei jedem extends-Einsatz Pflicht, um die aufgeloeste Definition zu pruefen.
# Aufgeloeste Konfiguration nach extends anzeigen
docker compose config
# Nur den web-Service isoliert pruefen
docker compose config web
4. include: ganze Compose-Dateien als Projektbausteine
Waehrend extends auf Service-Ebene arbeitet, operiert include auf Datei-Ebene. Eine Haupt-compose.yaml kann per include weitere, eigenstaendige Compose-Dateien referenzieren, deren Services dann Teil desselben Projekts werden, inklusive gemeinsamer Netzwerke und Volumes. Das eignet sich hervorragend fuer grosse Systeme, die aus mehreren fachlich getrennten Teilprojekten bestehen, etwa ein Frontend-Repository und ein Backend-Repository, die jeweils eine eigene Compose-Datei mitbringen.
Der entscheidende Unterschied zu mehreren -f-Flags ist, dass include direkt in der YAML-Datei selbst deklariert wird und damit versioniert, dokumentiert und ohne zusaetzliche Kommandozeilen-Parameter reproduzierbar ist. Ein Entwickler muss sich keine Liste von Flags merken, ein einfaches docker compose up reicht, weil die Datei selbst weiss, welche weiteren Dateien dazugehoeren.
# compose.yaml (Hauptprojekt)
include:
- path: ./backend/compose.yaml
- path: ./frontend/compose.yaml
env_file: ./frontend/.env
services:
reverse-proxy:
image: nginx:alpine
ports:
- "80:80"
depends_on:
- backend-api
- frontend-web
5. include versus mehrere -f-Flags: der praktische Unterschied
Mehrere -f-Flags wie docker compose -f a.yaml -f b.yaml up fuehren Dateien zusammen, indem sie Werte auf Schluesselebene ueberschreiben oder mergen, aehnlich wie bei einem Compose-Override. Das eignet sich fuer Varianten derselben Services, etwa eine Basisdatei plus eine Datei mit Debug-Ports fuer lokale Entwicklung. include dagegen fuehrt komplett getrennte Services aus unterschiedlichen Dateien zu einem gemeinsamen Projekt zusammen, ohne dass sich Schluessel ueberschreiben, weil die Service-Namen typischerweise unterschiedlich sind.
Ein haeufiger Fehler ist der Versuch, include fuer reine Override-Zwecke einzusetzen, etwa um in einer Staging-Datei nur den Port eines bestehenden Service zu aendern. Dafuer ist weiterhin -f mit mehreren Dateien oder ein compose.override.yaml das richtige Werkzeug. include ist fuer additive Komposition gedacht, nicht fuer punktuelle Ueberschreibung bestehender Services.
6. extends und include kombinieren
In groesseren Systemen ergaenzen sich beide Mechanismen: include bindet die Compose-Dateien mehrerer Teilprojekte zusammen, waehrend innerhalb eines Teilprojekts extends genutzt wird, um verwandte Services wie api und api-worker auf einer gemeinsamen Basisdefinition aufzubauen. Diese Trennung nach Verantwortlichkeit, service-interne Wiederverwendung per extends, projekt-uebergreifende Komposition per include, haelt beide Konzepte klar getrennt und verhindert, dass eine einzelne Datei zum unwartbaren Sammelsurium wird.
Ein reales Beispiel ist ein Monorepo mit drei Services (api, worker, scheduler), die alle vom selben Docker-Image abstammen und sich nur im Startkommando unterscheiden, plus zwei externen Teilprojekten (frontend, admin-panel) mit eigenen Compose-Dateien. Die api/worker/scheduler-Gruppe nutzt intern extends auf eine gemeinsame Basis, das Wurzel-Compose bindet frontend und admin-panel per include ein. Das Ergebnis ist eine flache, gut navigierbare Struktur trotz vieler beteiligter Teile.
# services/api-group.yaml -- nutzt extends fuer verwandte Services
services:
api-base:
image: myorg/api:latest
env_file: ./services/api.env
api:
extends: { service: api-base }
ports: ["8080:8080"]
worker:
extends: { service: api-base }
command: ["node", "worker.js"]
# compose.yaml -- nutzt include fuer Teilprojekte
include:
- path: ./services/api-group.yaml
- path: ./frontend/compose.yaml
- path: ./admin-panel/compose.yaml
7. Gemeinsame Netzwerke und Volumes ueber Dateigrenzen hinweg
Ein zentraler Vorteil von include gegenueber komplett getrennten Compose-Projekten ist, dass alle eingebundenen Dateien automatisch demselben Compose-Projekt angehoeren und dieselben top-level definierten Netzwerke und Volumes nutzen koennen, sofern sie identisch benannt sind. Ein backend-Netzwerk, das in der Hauptdatei definiert ist, steht damit auch Services aus eingebundenen Dateien zur Verfuegung, ohne dass man externe Netzwerke manuell per docker network create anlegen und referenzieren muss.
Bei separaten Projekten, die nur per Shell-Skript oder CI-Pipeline nacheinander gestartet werden, muss man dagegen explizit mit external: true auf ein zuvor angelegtes Netzwerk verweisen, was eine zusaetzliche Fehlerquelle darstellt, wenn die Reihenfolge der Starts nicht stimmt oder das Netzwerk noch nicht existiert. include nimmt diese Koordinationslast komplett ab, weil Compose die gesamte effektive Konfiguration in einem einzigen Durchlauf aufloest.
8. Praxisbeispiel: modulare Struktur in der CI-Pipeline nutzen
In CI-Pipelines zahlt sich diese Modularitaet besonders aus. Ein Job, der nur den Backend-Teil testen will, kann direkt docker compose -f services/api-group.yaml up aufrufen, ohne das komplette Projekt inklusive Frontend zu starten, waehrend ein End-to-End-Test-Job das Wurzel-compose.yaml mit allen include-Eintraegen nutzt. Dieselben Dateien dienen also sowohl fokussierten Unit-Test-Laeufen als auch vollstaendigen Integrationstests, ohne Duplikation.
Wichtig fuer CI ist zusaetzlich, dass relative Pfade in include immer relativ zur Datei aufgeloest werden, die den include-Eintrag enthaelt, nicht relativ zum aktuellen Arbeitsverzeichnis des CI-Runners. Wird das Repository in einen anderen Ordner ausgecheckt, bleiben die Pfade trotzdem stabil, solange die relative Struktur zwischen den Dateien erhalten bleibt, ein Detail, das bei absoluten Pfaden schnell zu kaputten Pipelines fuehrt.
# Nur den Backend-Teil isoliert testen
docker compose -f services/api-group.yaml up -d --wait
docker compose -f services/api-group.yaml exec api npm test
docker compose -f services/api-group.yaml down -v
# Vollstaendiges Projekt fuer End-to-End-Tests
docker compose up -d --wait
docker compose exec e2e-runner npm run test:e2e
9. Entscheidungshilfe: extends, include oder mehrere -f-Dateien
Die Wahl des richtigen Mechanismus haengt vom konkreten Wiederverwendungsziel ab. Fuer eng verwandte Services mit gemeinsamer Basis, etwa mehrere Worker-Varianten desselben Images, ist extends die praeziseste Loesung. Fuer eigenstaendige, aber zusammengehoerige Teilprojekte, die auch getrennt entwickelt und getestet werden koennen, ist include vorzuziehen. Fuer Umgebungsvarianten derselben Services, etwa Produktion versus lokale Entwicklung mit zusaetzlichen Debug-Ports, bleiben mehrere -f-Dateien oder ein automatisches compose.override.yaml die richtige Wahl.
Wer unsicher ist, sollte mit der einfachsten Loesung beginnen und erst modularisieren, wenn tatsaechlich Duplikation auftritt. Eine vorzeitige Aufteilung in viele kleine Dateien mit komplexen extends- und include-Ketten macht ein kleines Projekt oft schwerer verstaendlich als eine einzige, gut strukturierte Datei mit klaren Kommentaren. Modularitaet ist ein Mittel gegen Duplikation, kein Selbstzweck.
| Mechanismus | Wirkungsebene | Typischer Einsatz | Merge-Verhalten |
|---|---|---|---|
extends |
Einzelner Service | Verwandte Services auf gemeinsamer Basis | Listen gemergt, Skalare ueberschrieben |
include |
Ganze Compose-Datei | Eigenstaendige Teilprojekte zusammenfuehren | Additiv, kein Ueberschreiben gleicher Keys |
Mehrere -f-Flags |
Ganze Compose-Datei | Umgebungsvarianten derselben Services | Ueberschreiben plus Listen-Merge |
compose.override.yaml |
Ganze Compose-Datei (implizit) | Lokale Entwickler-Anpassungen | Automatisch geladen, wie -f |
Mironsoft
Container-Infrastruktur, CI-Pipelines und Deployment-Automatisierung
Docker-Setups, die im Team und in Produktion tragfähig bleiben?
Wir prüfen bestehende Dockerfiles und Compose-Stacks auf Sicherheitslücken, aufgeblähte Images und fragile Build-Pipelines und bauen daraus eine Container-Infrastruktur, die schnell baut, sicher läuft und im Team nachvollziehbar bleibt.
Dockerfile-Review
Multi-Stage-Builds, Layer-Caching und Image-Größe systematisch optimieren.
Security-Audit
Container-Isolation, Secrets-Handling und Image-Scanning gegen echte Angriffsflächen absichern.
CI/CD-Integration
Build-Pipelines, Registries und Deployment-Strategien für reproduzierbare Releases aufbauen.
10. Zusammenfassung
Compose extends und include: Das Wichtigste auf einen Blick
extends
Vererbt einzelne Service-Definitionen, unterstuetzt kein depends_on oder links aus der Basis.
include
Bindet ganze, eigenstaendige Compose-Dateien additiv in ein gemeinsames Projekt ein.
Vs. mehrere -f
include komponiert unterschiedliche Services, -f ueberschreibt Varianten derselben Services.
Faustregel
Erst modularisieren, wenn echte Duplikation auftritt, nicht praeventiv aufteilen.