Docker Compose extends und include: modulare Compose-Dateien richtig aufteilen
AI generated
FROM
RUN
Docker · Compose · Architektur
Modulare Compose-Dateien
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.

16 Min. Lesezeit Service-Vererbung Compose-Modularisierung Multi-Projekt-Setups

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.

11. FAQ: Compose extends und include: Das Wichtigste auf einen Blick

1Was ist der Hauptunterschied zwischen extends und include?
extends vererbt die Konfiguration eines einzelnen Service an einen anderen, auch dateiuebergreifend. include bindet eine komplette, eigenstaendige Compose-Datei mit allen ihren Services in das aktuelle Projekt ein.
2Kann ich mit extends auch depends_on vererben?
Nein, extends unterstuetzt weder depends_on noch links noch volumes_from aus der Basisdefinition, weil diese auf Services verweisen koennten, die im Kontext der Basisdatei nicht existieren. Diese Angaben muessen im erbenden Service erneut gesetzt werden.
3Wann sollte ich include statt mehrerer -f-Flags nutzen?
include eignet sich fuer eigenstaendige Teilprojekte mit unterschiedlichen Services, etwa Frontend und Backend aus separaten Repositories. Mehrere -f-Flags eignen sich besser fuer Varianten derselben Services, etwa Produktion versus lokale Entwicklung.
4Teilen sich per include eingebundene Dateien dasselbe Netzwerk?
Ja, sofern die Netzwerke gleich benannt sind, gehoeren alle per include eingebundenen Dateien automatisch zum selben Compose-Projekt und koennen dieselben top-level definierten Netzwerke und Volumes nutzen.
5Wie werden relative Pfade in include aufgeloest?
Relative Pfade in include werden relativ zu der Datei aufgeloest, die den include-Eintrag enthaelt, nicht relativ zum aktuellen Arbeitsverzeichnis des Aufrufs. Das macht Pfade stabil, auch wenn das Repository in einen anderen Ordner ausgecheckt wird.
6Kann ich extends und include gleichzeitig im selben Projekt nutzen?
Ja, beide lassen sich kombinieren. Ein uebliches Muster ist extends fuer verwandte Services innerhalb eines Teilprojekts und include, um mehrere solcher Teilprojekte zu einem Gesamtprojekt zusammenzufuehren.
7Wie pruefe ich, ob eine extends-Vererbung korrekt aufgeloest wurde?
Mit docker compose config wird die vollstaendig aufgeloeste Konfiguration inklusive aller extends-Vererbungen angezeigt. Man kann auch docker compose config nutzen, um nur einen einzelnen Service isoliert zu pruefen.
8Was passiert, wenn zwei per include eingebundene Dateien einen Service mit gleichem Namen haben?
Compose behandelt das als Konfigurationskonflikt, da include additiv gedacht ist und keine automatische Ueberschreibungslogik fuer gleichnamige Services zwischen eingebundenen Dateien vorsieht. Service-Namen sollten daher projektuebergreifend eindeutig sein.
9Eignet sich extends fuer stark unterschiedliche Services?
Nein, extends ist primaer fuer wirklich gemeinsame Basiskonfiguration gedacht. Bei Services, die sich fundamental unterscheiden, erzeugt extends eher Verwirrung als Einsparung, dort ist eine eigenstaendige Definition meist klarer.
10Sollte ich ein kleines Projekt vorsorglich mit extends und include modularisieren?
Nein, es empfiehlt sich, mit der einfachsten Loesung zu starten und erst zu modularisieren, wenn tatsaechlich Duplikation zwischen mehreren Compose-Dateien auftritt. Vorzeitige Aufteilung macht kleine Projekte oft unnoetig komplex.