Docker Compose mit mehreren Dateien richtig zusammenführen
Erfahren Sie, wie compose.override.yaml allein geladen wird, welche Reihenfolge beim Mergen gilt, warum ports Ports offen hält und wie include Dev und Prod trennt.
Was Compose mit mehr als einer Datei macht
Docker Compose kann ein einzelnes Projekt aus mehreren Dateien erstellen. Es liest sie in der Reihenfolge ein, in der es sie erhält, und führt sie zu einem einzigen Modell zusammen. Daher setzt eine spätere Datei jeden widersprüchlichen Wert außer Kraft. Über die Befehlszeile gibt es dafür zwei Mechanismen: eine Override-Datei, die Compose selbst lädt, und das -f-Flag, das Sie manuell übergeben. Ein dritter Mechanismus befindet sich in der Datei selbst: das include-Element. Es funktioniert anders als die beiden anderen.
Beim Zusammenführen werden Werte nicht einfach überschrieben. Zuordnungen werden Schlüssel für Schlüssel zusammengeführt, Sequenzen werden angehängt, und eine kleine Gruppe von Feldern wird vollständig ersetzt. Dieser Unterschied verursacht die meisten unerwarteten Ergebnisse. Die Liste ports sorgt dabei besonders häufig für Probleme.
Alle folgenden Angaben setzen Compose v2 und das docker compose-Plugin voraus, nicht das alte docker-compose-Skript. Führen Sie docker compose version aus, um dies zu prüfen. Wenn Sie noch keine Compose-Datei erstellt haben, beginnen Sie mit dem Grundlagenleitfaden zu Docker Compose und kehren Sie anschließend hierher zurück.
Die Override-Datei, die Compose ohne Angabe lädt
Führen Sie docker compose up ohne das Flag -f aus. Compose durchsucht dann das Arbeitsverzeichnis und anschließend dessen übergeordnete Verzeichnisse nach compose.yaml oder docker-compose.yaml. Wenn sich eine Override-Datei neben der Basisdatei befindet, lädt Compose sie automatisch als zweite Datei.
ls compose.yaml compose.override.yaml
docker compose up -dWenn beide Dateien vorhanden sind, entspricht das der manuellen Angabe beider Dateien.
docker compose -f compose.yaml -f compose.override.yaml up -dCompose erkennt die Namen compose.override.yaml, compose.override.yml sowie die älteren Namen docker-compose.override.yml und docker-compose.override.yaml. Jede andere Datei, zum Beispiel compose.dev.yaml, wird nur geladen, wenn Sie sie mit -f angeben.
Sobald Sie ein -f übergeben, endet das automatische Laden. docker compose -f compose.yaml up liest genau diese eine Datei und ignoriert die Override-Datei. Auf dieser Eigenschaft basiert das später in diesem Leitfaden beschriebene Muster für Entwicklung und Produktion.
Auf einem Server kann sich das in beide Richtungen auswirken. Eine Override-Datei im Deploy-Verzeichnis wird von jedem ohne weitere Optionen ausgeführten docker compose-Befehl geladen, der aus diesem Verzeichnis gestartet wird, auch von dem Befehl Ihres Cronjobs. So bindet ein Produktions-Stack am Ende ein Quellverzeichnis ein, das niemand ausliefern wollte. Führen Sie nach jedem Deploy docker compose config aus und prüfen Sie die Ausgabe.
Reihenfolge mit -f und Auflösung relativer Pfade
Compose erstellt die Konfiguration in der Reihenfolge, in der Sie die Dateien angeben. Nachfolgende Dateien überschreiben ihre Vorgänger und ergänzen sie. Von links nach rechts gilt: Die letzte Angabe gewinnt.
docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -dJeder Befehl in diesem Projekt benötigt dieselbe Dateiliste. Wenn Sie up mit zwei Dateien und logs mit einer Datei ausführen, verwenden Sie ein anderes zusammengeführtes Modell. Dadurch kann ein Dienst schnell als nicht vorhanden gemeldet werden. Legen Sie die Liste stattdessen einmal mit der Umgebungsvariable COMPOSE_FILE fest.
export COMPOSE_FILE=compose.yaml:compose.prod.yaml
docker compose config
docker compose up -dUnter Linux ist das Trennzeichen :. Mit COMPOSE_PATH_SEPARATOR ändern Sie es. COMPOSE_FILE kann auch in der Projektdatei .env stehen. Dadurch gehört die Einstellung zum Checkout und nicht zur Shell-Historie. Eine explizit in der Befehlszeile gesetzte Einstellung hat Vorrang vor der Umgebungsvariable.
Nun zur Regel, die Bind-Mounts beschädigt. Wenn Sie mehrere Dateien mit -f verwenden, werden alle relativen Pfade in allen Dateien relativ zum Verzeichnis der ersten Datei aufgelöst. Sie können ./data:/var/lib/postgresql/data in deploy/prod/compose.prod.yaml angeben, aber Compose sucht ./data weiterhin neben der Basisdatei. Docker erstellt dann am falschen Pfad ein leeres Verzeichnis. Der Container startet darin ohne Daten. Das sieht wie ein Datenverlust aus, ist aber keiner. Übergeben Sie --project-directory, um den Basispfad selbst festzulegen. Alternativ können Sie include verwenden. Damit wird jede Datei relativ zu ihrem eigenen Verzeichnis aufgelöst.
Der Projektname wird aus demselben Basisverzeichnis abgeleitet. Wenn Sie die Reihenfolge der Dateien ändern, kann sich daher der Projektname ändern. Bei einem neuen Projektnamen entstehen neue Containernamen und neue Volumenamen. Das alte Volume bleibt unter dem alten Namen auf der Festplatte erhalten. Legen Sie den Namen stattdessen mit name: auf oberster Ebene in der Basisdatei fest.
name: myappWelche Felder zusammengeführt und welche ersetzt werden
Compose führt Felder anhand des Werttyps zusammen, nicht anhand des Feldnamens.
- Felder mit einem einzelnen Wert werden ersetzt.
image,command,entrypointundmem_limitübernehmen den späteren Wert vollständig. Sie können kein Argument an eincommandanhängen, weil die Überschreibung die gesamte Zeile ersetzt. - Zuordnungen werden Schlüssel für Schlüssel zusammengeführt.
environment,labels,volumesunddevicesbehalten jeden Schlüssel aus beiden Dateien. Bei einem Schlüssel, der in beiden Dateien vorhanden ist, gilt der Wert aus der späteren Datei. Beienvironmentundlabelsist der Schlüssel der Variablen- oder Labelname. Beivolumesunddevicesist der Schlüssel der Containerpfad. - Sequenzen werden angehängt.
dns,dns_search,expose,tmpfsundexternal_linkswerden verkettet. Eine Basisdatei mitexpose: ["3000"]und eine Überschreibungsdatei mit["4000", "5000"]ergeben["3000", "4000", "5000"].
Vier Sequenzen verwenden einen Identitätsschlüssel. Daher werden Einträge mit übereinstimmendem Schlüssel zusammengeführt, statt angehängt zu werden. volumes, secrets und configs werden anhand von target abgeglichen. ports wird anhand der Kombination aus ip, target, published und protocol abgeglichen.
Lesen Sie die Regel für ports zweimal, weil sie die häufigste Fehlerquelle ist. Zwei Port-Einträge gelten nur dann als derselbe Eintrag, wenn alle vier Bestandteile übereinstimmen. Wenn Sie einen davon ändern, erkennt Compose einen zweiten, unabhängigen Port und behält daher beide.
Warum Ihr Port nach dem Override weiterhin veröffentlicht ist
Eine Basisdatei, die einen Dienst auf jeder Schnittstelle veröffentlicht:
services:
web:
image: nginx:1.27
ports:
- "8080:80"Ein Override, das den Dienst nur an localhost bindet, weil davor ein Reverse Proxy eingesetzt wird:
services:
web:
ports:
- "127.0.0.1:8080:80"Prüfen Sie das Ergebnis, bevor Sie davon ausgehen, dass es funktioniert hat.
docker compose -f compose.yaml -f compose.prod.yaml configBeide Einträge sind in der Ausgabe enthalten. Der Teil ip unterscheidet sich, 0.0.0.0 gegenüber 127.0.0.1. Daher behandelt der Merge sie als zwei verschiedene Ports, und die öffentliche Bindung, die Sie entfernen wollten, ist weiterhin im Modell enthalten. Das ist bei Docker wichtiger als in anderen Umgebungen, weil ein veröffentlichter Port vor Ihren Firewall-Regeln in iptables geschrieben wird. Der Mechanismus wird in warum veröffentlichte Docker-Ports ufw umgehen behandelt.
Es gibt zwei Lösungen. Die explizite Lösung ist das Tag !override. Es ersetzt das gesamte Attribut und überspringt die Merge-Regeln:
services:
web:
ports: !override
- "127.0.0.1:8080:80"!override erfordert Compose v2.24.4 oder neuer. Die portable Lösung benötigt kein Tag: Lassen Sie ports vollständig aus der Basisdatei heraus und deklarieren Sie es nur in den umgebungsspezifischen Dateien. Wenn nichts zusammengeführt wird, kann auch nichts nach außen gelangen. Dieses Muster wird im folgenden vollständigen Beispiel verwendet.
Löschen eines Werts aus der Basiskonfiguration
!reset entfernt ein Attribut und setzt es auf den Standardwert oder auf null zurück. Der Befehl erwartet einen Wert und ignoriert ihn daher. Geben Sie einen gültigen, leeren Wert an.
services:
web:
ports: !reset []
environment:
DEBUG: !reset null!reset erfordert Compose v2.24 oder neuer. Verwenden Sie es, wenn Sie die Basiskonfiguration nicht bearbeiten können, zum Beispiel bei einem eingebundenen Fragment eines Anbieters.
include, für aus Teilen zusammengesetzte Stacks
include bindet eine weitere Compose-Anwendung in Ihr Modell ein. Es ist ein Element der obersten Ebene und kein Flag.
include:
- path: ../commons/compose.yamlJeder Pfad in include wird als eigenes Compose-Anwendungsmodell mit einem eigenen Projektverzeichnis geladen. Daher werden relative Pfade innerhalb dieser Datei relativ zum Verzeichnis der jeweiligen Datei aufgelöst. Das ist der wesentliche Unterschied zu -f und der Grund, warum include das geeignete Werkzeug ist, wenn sich das Fragment in einem anderen Verzeichnis oder Repository befindet.
Die Langform akzeptiert Unteroptionen.
include:
- path:
- ../monitoring/compose.yaml
- ../monitoring/compose.vps.yaml
project_directory: ../monitoring
env_file: ../monitoring/.envpath akzeptiert eine Liste. Diese Dateien werden nach den normalen Regeln zusammengeführt, bevor das Ergebnis in Ihr Modell übernommen wird. project_directory legt den Basispfad fest, der zum Auflösen relativer Pfade in der eingebundenen Datei verwendet wird. env_file weist der eingebundenen Datei eigene Variablen für die Interpolation zu. Dadurch wird verhindert, dass ein gemeinsames Fragment unbemerkt die .env Ihres Projekts einliest. include erfordert Compose v2.20.0 oder neuer.
Doppelte Ressourcennamen zwischen Ihrer Datei und einer eingebundenen Datei werden als Fehler gemeldet und nicht stillschweigend zusammengeführt. Das ist beabsichtigt. Um eine Deklaration aus einer eingebundenen Datei zu ändern, nehmen Sie die Änderung in compose.override.yaml vor. Die Überschreibung wird auf das zusammengesetzte Modell angewendet und kann daher eingebundene Ressourcen ändern, ohne mit ihnen zu kollidieren.
Kurz gesagt: include setzt separate Anwendungen zusammen, -f legt Konfiguration über eine einzelne Anwendung.
Eine Trennung von Entwicklung und Produktion auf einem VPS
Hier ist das vollständige Muster in drei Dateien. Die Basisdatei definiert, was überall gilt, und veröffentlicht überhaupt keine Ports.
name: myapp
services:
app:
image: ghcr.io/example/app:1.4.2
environment:
DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
LOG_LEVEL: info
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16
environment:
POSTGRES_USER: app
POSTGRES_DB: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
db_data:Die Bedingung depends_on sorgt dafür, dass die Anwendung auf eine tatsächlich antwortende Datenbank wartet und nicht nur auf einen vorhandenen Container. Das wird unter Healthchecks und depends_on-Bedingungen erklärt. POSTGRES_PASSWORD wird aus der Projektdatei .env interpoliert. Diese Datei gehört niemals in git. Unter Env-Dateien und Compose-Secrets finden Sie die sichereren Varianten.
Als Nächstes kommt compose.override.yaml, das Compose automatisch lädt. Dies ist die Datei für die Entwicklung.
services:
app:
build: .
command: npm run dev
environment:
LOG_LEVEL: debug
ports:
- "3000:3000"
volumes:
- ./src:/app/src
db:
ports:
- "127.0.0.1:5432:5432"Auf einem Laptop führt ein einfaches docker compose up diese beiden Dateien zusammen. command ersetzt den Standardwert des Images, weil dieser Wert nur einmal vorkommt. LOG_LEVEL ersetzt info, weil environment nach Schlüssel zusammenführt. Das Bind-Mount und die beiden veröffentlichten Ports sind reine Ergänzungen. Der Datenbankport ist an localhost gebunden. Dadurch bietet ein Laptop in einem gemeinsam genutzten Netzwerk PostgreSQL nicht für andere Geräte an.
Zum Schluss kommt compose.prod.yaml. Sein Name wird von Compose nicht automatisch gesucht. Dadurch wird die Datei niemals versehentlich geladen.
services:
app:
ports:
- "127.0.0.1:8000:3000"
deploy:
resources:
limits:
memory: 512MAuf dem VPS geben Sie beide Dateien an. Durch diese explizite Angabe wird das Override ausgeschlossen.
docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -d
docker compose -f compose.yaml -f compose.prod.yaml psps sollte beide Services als laufend anzeigen. db sollte dabei (healthy) anzeigen. Da Sie -f angegeben haben, wurde compose.override.yaml nicht gelesen. Deshalb können der Entwicklungsbefehl, das Bind-Mount des Quellverzeichnisses und der öffentliche Port 3000 die Produktion nicht erreichen, obwohl die Datei im selben Verzeichnis liegt. Port 8000 ist nur an localhost gebunden und damit für einen Proxy bereit. Wenn Sie den zweiten Service hinzufügen, siehe Mehrere Anwendungen hinter Traefik betreiben.
Setzen Sie COMPOSE_FILE=compose.yaml:compose.prod.yaml in der .env des Servers. Danach können Sie für die übrigen Befehle wieder einfach docker compose logs -f app verwenden.
Lesen Sie das zusammengeführte Modell vor der Bereitstellung
docker compose config gibt das vollständig zusammengeführte und interpolierte Modell aus. Es handelt sich nicht um eine Vorschau. Es ist exakt die Eingabe, die Compose verarbeitet. Wenn die Ausgabe von Ihrer Erwartung abweicht, ist die Ausgabe maßgeblich.
docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml config --no-interpolate
docker compose -f compose.yaml -f compose.prod.yaml config --services--no-interpolate lässt ${VAR} nicht expandiert. Verwenden Sie den Befehl, bevor Sie die Ausgabe an anderer Stelle einfügen, da config alle aufgelösten Secrets im Klartext ausgibt. --services listet nur die Servicenamen auf. Damit können Sie schnell prüfen, ob ein include die erwarteten Inhalte eingebunden hat.
Fehlerzustände und ihre sichtbaren Auswirkungen
no configuration file provided: not found. Compose hat nichts zum Lesen gefunden. Sie befinden sich außerhalb des Projektverzeichnisses, oder COMPOSE_FILE bezeichnet einen Pfad, der nicht existiert. Compose durchsucht übergeordnete Verzeichnisse nach der standardmäßigen Basisdatei. Nach einer Datei, die Sie selbst angegeben haben, sucht Compose jedoch nirgends.
WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. Die Interpolation wird anhand der .env-Datei des Projekts und der Shell-Umgebung aufgelöst. Das Projektverzeichnis ist dabei das Verzeichnis der ersten -f-Datei. Wenn Sie die Bereitstellung aus einem anderen Verzeichnis als dem Verzeichnis mit .env ausführen, erhalten Sie diese Warnung. Anschließend verweigert die Datenbank jede Verbindung.
Ihre Änderung an der Override-Datei wird in docker compose config nicht angezeigt. Entweder haben Sie -f übergeben. Dadurch wird das automatische Laden von Override-Dateien deaktiviert. Oder Compose hat compose.yaml in einem übergeordneten Verzeichnis gefunden, und Ihre Override-Datei befindet sich nicht daneben. Wenn Sie docker compose config ohne weitere Argumente ausführen, erfahren Sie, welches Modell Compose tatsächlich erstellt.
Ein Bind-Mount ist leer, und Docker hat ein Verzeichnis erstellt, das Sie nicht angefordert haben. Der relative Pfad wurde anhand des Verzeichnisses der ersten Datei aufgelöst. Korrigieren Sie den Pfad, übergeben Sie --project-directory, oder verschieben Sie das Fragment hinter include.
Container werden mit neuen Namen gestartet, und ein Volume scheint leer zu sein. Der Projektname hat sich geändert, weil der Projektname dem Verzeichnis der ersten Datei folgt. Fügen Sie der Basisdatei auf oberster Ebene name: hinzu. Dadurch bleibt die Namensgebung stabil. Das alte Volume befindet sich weiterhin unter dem alten Präfix. docker volume ls zeigt es an.
Ein Port, den Sie im Override entfernt haben, ist weiterhin geöffnet. Das Zusammenführen mit ports hat den Eintrag angehängt, anstatt ihn zu ersetzen. Bestätigen Sie dies mit docker compose config. Verwenden Sie anschließend entweder !override oder verschieben Sie ports aus der Basisdatei.
FAQ
Lädt Compose compose.override.yaml automatisch?
Ja, wenn Sie docker compose ohne das Flag -f ausführen. Compose durchsucht das Arbeitsverzeichnis und dessen übergeordnete Verzeichnisse nach compose.yaml oder docker-compose.yaml. Wenn sich eine Override-Datei daneben befindet, wird sie als zweite Datei geladen. Die erkannten Namen sind compose.override.yaml, compose.override.yml, docker-compose.override.yml und docker-compose.override.yaml. Die Übergabe eines beliebigen -f deaktiviert dieses Verhalten. Dann liest docker compose -f compose.yaml up nur eine Datei.
In welcher Reihenfolge werden mehrere -f-Dateien zusammengeführt?
Von links nach rechts. Compose erstellt die Konfiguration in der Reihenfolge, in der Sie die Dateien angeben. Jede Datei überschreibt Werte aus den vorherigen Dateien und ergänzt sie. Bei Konflikten hat daher die letzte Datei in der Befehlszeile Vorrang. Für jeden Befehl in diesem Projekt muss dieselbe Liste verwendet werden. Dafür ist COMPOSE_FILE=compose.yaml:compose.prod.yaml vorgesehen.
Warum wird mein Port weiterhin veröffentlicht, nachdem ich ihn überschrieben habe?
Weil ports-Einträge anhand der vollständigen Kombination aus ip, target, published und protocol identifiziert werden. Eine Überschreibung von 127.0.0.1:8080:80 gegenüber einer Basis von 8080:80 unterscheidet sich im Teil ip. Compose behandelt sie daher als zweiten Port und behält beide bei. Führen Sie docker compose config aus. Dann sehen Sie die beiden Einträge. Verwenden Sie ports: !override mit Compose v2.24.4 oder neuer. Alternativ können Sie ports aus der Basisdatei entfernen, sodass nichts zusammengeführt werden muss.
Was ist der Unterschied zwischen include und -f?
-f führt mehrere Dateien zu einer Anwendung zusammen. Relative Pfade in allen Dateien werden dabei relativ zum Verzeichnis der ersten Datei aufgelöst. include bindet eine separate Compose-Anwendung ein. Jede eingebundene Datei behält ihr eigenes Projektverzeichnis. Ihre relativen Pfade werden daher relativ zu diesem Verzeichnis aufgelöst. Verwenden Sie -f für Umgebungs-Ebenen Ihres eigenen Stacks und include für ein Fragment, das an anderer Stelle gepflegt wird. include erfordert Compose v2.20.0 oder neuer.
Wie entferne ich einen Wert, den die Basisdatei festlegt?
Verwenden Sie das Tag !reset mit Compose v2.24 oder neuer. Schreiben Sie ports: !reset [] oder MY_VAR: !reset null in die überschreibende Datei. Dann wird das Attribut auf seinen Standardwert oder auf null zurückgesetzt. Der Wert, den Sie für das Tag angeben, ist erforderlich, wird aber ignoriert. Wenn Sie ein Attribut ersetzen statt leeren möchten, verwenden Sie !override. Dafür ist v2.24.4 oder neuer erforderlich.