Docker Compose met meerdere bestanden gebruiken
Leer hoe Docker Compose bestanden samenvoegt, waarom de ports-lijst vaak voor problemen zorgt en hoe u de include-functie correct inzet voor uw ontwikkel- en productieomgeving.
Wat Compose doet met meer dan één bestand
Docker Compose kan één project opbouwen uit meerdere bestanden. Het leest deze in de volgorde waarin ze worden aangeleverd en voegt ze samen tot één model, waarbij een later bestand voorrang krijgt bij conflicterende waarden. Twee mechanismen doen dit vanaf de opdrachtregel: een override-bestand dat Compose automatisch laadt, en de -f-vlag die u handmatig meegeeft. Een derde bevindt zich in het bestand zelf, het include-element, en dit werkt anders dan de voorgaande twee.
Het samenvoegen is geen eenvoudige overschrijving. Mappings worden per sleutel samengevoegd, sequenties worden toegevoegd en een kleine set velden wordt in zijn geheel vervangen. Dat verschil zorgt voor verrassingen, en de ports-lijst is het punt waar bijna iedereen tegenaan loopt.
Alles hieronder gaat uit van Compose v2, de docker compose-plugin in plaats van het oude docker-compose-script. Voer docker compose version uit om dit te controleren. Als u nog geen Compose-bestand heeft geschreven, begin dan bij de basisgids voor Docker Compose en keer daarna terug.
Het override-bestand dat Compose automatisch laadt
Voer docker compose up uit zonder -f-vlag en Compose doorzoekt de werkmap, en vervolgens de bovenliggende mappen, naar compose.yaml of docker-compose.yaml. Als er een override-bestand naast het basisbestand staat, laadt Compose dit automatisch als tweede.
ls compose.yaml compose.override.yaml
docker compose up -dWanneer beide bestanden aanwezig zijn, is dit hetzelfde als wanneer u ze handmatig opgeeft.
docker compose -f compose.yaml -f compose.override.yaml up -dDe namen die Compose herkent zijn compose.override.yaml, compose.override.yml, en de oudere docker-compose.override.yml en docker-compose.override.yaml. Elke andere naam, bijvoorbeeld compose.dev.yaml, wordt alleen geladen wanneer u deze expliciet benoemt met -f.
Zodra u één -f opgeeft, stopt het automatisch laden. docker compose -f compose.yaml up leest dan uitsluitend dat ene bestand en negeert de override; dit is de eigenschap waarop het dev- en prod-patroon verderop in deze handleiding is gebaseerd.
Dit werkt op een server beide kanten op. Een overridebestand dat in de deploydirectory blijft staan, wordt geladen door elke kale opdracht docker compose die vanuit die directory wordt uitgevoerd, ook door de opdracht die uw cronjob uitvoert. Zo kan een productiestack uiteindelijk een brondirectory binden die niemand had bedoeld mee te leveren. Voer na elke deploy docker compose config uit en lees de uitvoer. Wanneer die deploy zonder toezicht wordt uitgevoerd, helpt de controle alleen als u een melding krijgt dat er iets is misgegaan. Daarvoor gebruikt u een pushkanaal, zoals een zelfgehoste ntfy-server, waarnaar een cronjob of een systemd-OnFailure-unit een bericht kan sturen.
Volgorde met -f en de resolutie van relatieve paden
Compose bouwt de configuratie op in de volgorde waarin u de bestanden aanlevert; opeenvolgende bestanden overschrijven of vullen de voorgaande aan. Van links naar rechts geldt: het laatste bestand wint.
docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -dElk commando in dat project vereist dezelfde lijst met bestanden. Voer up uit met twee bestanden en logs met één, en u communiceert met een ander samengevoegd model. Dit is een snelle manier om een service te krijgen waarvan Compose beweert dat deze niet bestaat. De risico's nemen toe bij een stack waarvan de upgrades als eenmalige commando's worden uitgevoerd, zoals de databasemigratiestap in een zelfgehoste Chatwoot-supportdesk, waarbij een docker compose run met de verkeerde bestandslijst stilletjes een ander model target dan het model dat uw services momenteel gebruiken. Stel de lijst liever eenmalig in met de omgevingsvariabele COMPOSE_FILE.
export COMPOSE_FILE=compose.yaml:compose.prod.yaml
docker compose config
docker compose up -dHet scheidingsteken is : op Linux, en COMPOSE_PATH_SEPARATOR wijzigt dit. COMPOSE_FILE kan ook in het projectbestand .env worden opgenomen, waardoor het onderdeel wordt van de checkout in plaats van uw shell-geschiedenis. Alles wat expliciet op de commandoregel wordt ingesteld, heeft voorrang op de omgevingsvariabele.
Dan de regel die bind mounts kan verstoren. Wanneer u meerdere bestanden gebruikt met -f, worden alle relatieve paden in al die bestanden opgelost ten opzichte van de map van het eerste bestand, niet ten opzichte van het bestand waarin ze staan. Schrijf ./data:/var/lib/postgresql/data in deploy/prod/compose.prod.yaml en Compose zoekt nog steeds naar ./data naast het basisbestand. Docker maakt vervolgens een lege map aan op dat verkeerde pad en de container start zonder inhoud, wat lijkt op dataverlies, maar dat niet is. Geef --project-directory mee om het basispad zelf in te stellen, of gebruik include, waarmee elk bestand wordt opgelost ten opzichte van zijn eigen map.
De projectnaam is afgeleid van diezelfde basisdirectory, dus het wijzigen van het eerste bestand kan het project hernoemen. Een hernoemd project betekent nieuwe containernamen en nieuwe volumenamen, terwijl het oude volume onder de oude naam op de schijf blijft staan. Zet dit vast met een name: op het hoogste niveau in het basisbestand.
name: myappWelke velden worden samengevoegd en welke worden vervangen
Compose voert samenvoegingen uit op basis van het type waarde, niet op basis van de naam van het veld.
- Velden met één waarde worden vervangen.
image,command,entrypointenmem_limitnemen de latere waarde volledig over. U kunt geen argument toevoegen aan eencommand, omdat de override de volledige regel herschrijft. - Mappings worden per sleutel samengevoegd.
environment,labels,volumesendevicesbehouden elke sleutel uit beide bestanden, waarbij het latere bestand de voorkeur krijgt bij sleutels die in beide voorkomen. Voorenvironmentenlabelsis de sleutel de naam van de variabele of het label. Voorvolumesendevicesis de sleutel het containerpad. - Sequenties worden toegevoegd.
dns,dns_search,expose,tmpfsenexternal_linksworden samengevoegd. Een basisbestand metexpose: ["3000"]dat wordt samengevoegd met een override met["4000", "5000"], resulteert in["3000", "4000", "5000"].
Vier sequenties bevatten een identiteitssleutel, waardoor items die overeenkomen op die sleutel worden samengevoegd in plaats van toegevoegd. volumes, secrets en configs komen overeen op target. ports komt overeen op de combinatie van ip, target, published en protocol.
Lees die ports-regel twee keer, want dit is de valkuil. Twee poortvermeldingen zijn alleen hetzelfde item als alle vier die onderdelen overeenkomen. Wijzig er één en Compose ziet een tweede, ongerelateerde poort, waardoor beide behouden blijven.
Waarom uw poort na de override nog steeds gepubliceerd is
Een basisbestand dat een service op elke interface publiceert:
services:
web:
image: nginx:1.27
ports:
- "8080:80"Een override die is geschreven om deze alleen aan localhost te binden, omdat er een reverse proxy voor zal staan:
services:
web:
ports:
- "127.0.0.1:8080:80"Controleer het resultaat voordat u aanneemt dat het heeft gewerkt.
docker compose -f compose.yaml -f compose.prod.yaml configBeide vermeldingen staan in de output. Het ip-gedeelte verschilt, 0.0.0.0 tegenover 127.0.0.1, dus voor de merge zijn het twee verschillende poorten en de publieke binding die u probeerde te verwijderen staat nog steeds in het model. Dat is bij Docker belangrijker dan elders, omdat een gepubliceerde poort in iptables wordt geschreven vóór uw firewallregels. Het mechanisme wordt behandeld in waarom gepubliceerde Docker-poorten ufw omzeilen.
Er zijn twee oplossingen. De expliciete methode is de !override-tag, die het volledige attribuut vervangt en de merge-regels overslaat:
services:
web:
ports: !override
- "127.0.0.1:8080:80"!override vereist Compose v2.24.4 of nieuwer. De draagbare oplossing heeft helemaal geen tag nodig: houd ports volledig uit het basisbestand en declareer deze alleen in de omgevingsspecifieke bestanden. Niets om samen te voegen betekent niets dat kan lekken. Dat is het patroon dat in het onderstaande uitgewerkte voorbeeld wordt gebruikt.
Een waarde verwijderen uit de basisbestandsset
!reset verwijdert een attribuut en zet dit terug naar de standaardwaarde of naar null. Het commando vereist een waarde, maar negeert deze; voer daarom een geldige, lege waarde in.
services:
web:
ports: !reset []
environment:
DEBUG: !reset null!reset vereist Compose v2.24 of nieuwer. Gebruik dit wanneer u het basisbestand niet zelf kunt bewerken, bijvoorbeeld bij een fragment van een leverancier dat u importeert. Een gepubliceerde upstream-stack is precies zo'n geval: het Compose-bestand achter een zelfgehoste AFFiNE-werkruimte declareert vier containers die u niet zelf heeft geschreven, en met !reset kunt u één attribuut op een van deze containers wissen zonder het bestand te forken en de taak van het bijhouden ervan over te nemen.
include, voor stacks samengesteld uit onderdelen
include trekt een andere Compose-applicatie in uw model. Het is een top-level element, geen flag.
include:
- path: ../commons/compose.yamlElk pad in include wordt geladen als een eigen Compose-applicatiemodel, met een eigen projectmap, waardoor relatieve paden in dat bestand worden opgelost ten opzichte van de map van dat specifieke bestand. Dat is het wezenlijke verschil met -f, en de reden waarom include het juiste hulpmiddel is wanneer het fragment zich in een andere map of een andere repository bevindt. Dit is de gebruikelijke vorm van een vendor-stack die u niet zelf heeft geschreven: het multi-service Compose-bestand achter een self-hosted Authentik SSO-installatie kan in zijn eigen map staan met zijn eigen relatieve paden intact, terwijl uw bestand zich richt op uw eigen services.
De lange vorm accepteert sub-opties.
include:
- path:
- ../monitoring/compose.yaml
- ../monitoring/compose.vps.yaml
project_directory: ../monitoring
env_file: ../monitoring/.envpath accepteert een lijst, en deze bestanden worden samengevoegd volgens de standaardregels voordat het resultaat wordt toegevoegd aan uw model. project_directory stelt het basispad in dat wordt gebruikt om relatieve paden in het geïncludeerde bestand op te lossen. env_file geeft het geïncludeerde bestand eigen variabelen voor interpolatie, wat voorkomt dat een gedeeld fragment stilletjes de .env van uw project uitleest. include vereist Compose v2.20.0 of nieuwer. Dezelfde opties zijn geschikt voor een single-container add-on voor een stack die u al draait, zoals Halcyon, dat een Jellyfin-bibliotheek voorziet van een jaren 90 videotheek-uiterlijk: het bestand behoudt zijn eigen image-tag en zijn eigen env_file, waardoor een upgrade nooit betekent dat u het bestand waarin uw mediastack leeft hoeft aan te passen.
Dubbele resourcenamen tussen uw bestand en een geïncludeerd bestand worden gerapporteerd als een fout in plaats van stilletjes samengevoegd, en dat is een bewuste keuze. Om iets te wijzigen dat een geïncludeerd bestand declareert, plaatst u de wijziging in compose.override.yaml: de override wordt toegepast op het samengestelde model, waardoor het geïncludeerde resources kan aanpassen zonder ermee in conflict te komen. Die gewoonte loont het meest bij een stack waarvan het upstream-bestand bij elke release wordt overschreven, zoals de multi-container fotoservers die worden vergeleken in PhotoPrism versus Immich, waarbij een localhost-binding of een extra volume thuishoort in uw override in plaats van in het bestand dat de volgende upgrade zal vervangen.
Kort samengevat: include combineert afzonderlijke applicaties, -f legt configuratie als een laag over één applicatie heen.
Een dev- en prod-omgeving scheiden op één VPS
Hier is het volledige patroon in drie bestanden. Het basisbestand declareert wat overal geldt en publiceert geen enkele poort.
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:De depends_on-conditie zorgt ervoor dat de applicatie wacht op een database die daadwerkelijk antwoordt, in plaats van alleen op een container die bestaat; dit wordt uitgelegd in healthchecks en depends_on condities. POSTGRES_PASSWORD wordt geïnterpoleerd vanuit het .env-bestand van het project, dat nooit in git thuishoort. Zie env-bestanden en Compose secrets voor de veiligere varianten.
Vervolgens compose.override.yaml, dat Compose automatisch inlaadt. Dit is het bestand voor de ontwikkelaar.
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"Op een laptop voegt een kale docker compose up deze twee bestanden samen. command vervangt de standaard image omdat het een enkelvoudige waarde is. LOG_LEVEL vervangt info omdat environment samenvoegt op basis van sleutel. De bind mount en de twee gepubliceerde poorten zijn pure toevoegingen, en de databasepoort is gebonden aan localhost zodat een laptop op een gedeeld netwerk PostgreSQL niet aan de hele ruimte aanbiedt.
Als laatste compose.prod.yaml. De naam hiervan wordt niet automatisch door Compose gezocht, dus het wordt nooit per ongeluk geladen.
services:
app:
ports:
- "127.0.0.1:8000:3000"
deploy:
resources:
limits:
memory: 512MOp de VPS specificeert u beide bestanden; door ze expliciet te benoemen, wordt de override uitgesloten.
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 zou beide services als actief moeten weergeven, waarbij db de status (healthy) toont. Omdat u -f heeft meegegeven, werd compose.override.yaml niet gelezen. Hierdoor kunnen het dev-commando, de source bind mount en de publieke poort 3000 de productieomgeving niet bereiken, ook al staat het bestand in dezelfde map. Poort 8000 is alleen beschikbaar op localhost, klaar voor een proxy: zie meerdere apps draaien achter Traefik wanneer u de tweede service toevoegt.
Stel COMPOSE_FILE=compose.yaml:compose.prod.yaml in in de .env van de server en de rest van uw commando's kunnen weer als gewone docker compose logs -f app worden uitgevoerd.
Een stack met één service krijgt dezelfde structuur, omdat een zelf-gehoste openGym workout tracker via TLS moet antwoorden achter een proxy voordat u de eerste passkey registreert. Een basisbestand zonder ports erin voorkomt dat een onbedoelde publieke binding de proxy voor is.
Lees het samengevoegde model voordat u implementeert
docker compose config toont het volledig samengevoegde en volledig geïnterpoleerde model. Dit is geen preview. Het is de exacte invoer waarop Compose zal handelen; wanneer de uitvoer afwijkt van uw verwachting, is de uitvoer correct.
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 laat ${VAR} onuitgebreid. Gebruik dit voordat u uitvoer ergens plakt, omdat een standaard config elk opgelost geheim in leesbare tekst toont. --services geeft alleen de servicenamen weer, wat een snelle manier is om te bevestigen dat een include heeft binnengehaald wat u verwachtte.
Foutmodi en wat u zult zien
no configuration file provided: not found. Compose kon niets vinden om te lezen. U bevindt zich buiten de projectmap, of COMPOSE_FILE verwijst naar een pad dat niet bestaat. Compose doorzoekt bovenliggende mappen naar het standaard basisbestand, maar zoekt nergens naar een bestand dat u zelf heeft opgegeven.
WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. Interpolatie wordt opgelost aan de hand van het project .env-bestand en de shell-omgeving; de projectmap is hier de map van het eerste -f-bestand. Deployen vanuit een andere map dan de map die .env bevat, geeft deze waarschuwing en resulteert in een database die elke verbinding weigert.
Uw override-aanpassing is niet zichtbaar in docker compose config. Of u heeft -f meegegeven, wat het automatisch laden van overrides uitschakelt, of Compose heeft compose.yaml gevonden in een bovenliggende map en uw override-bestand staat niet in dezelfde map. Het uitvoeren van docker compose config zonder verdere argumenten toont u welk model Compose daadwerkelijk opbouwt.
Een bind mount is leeg en Docker heeft een map aangemaakt waar u niet om vroeg. Het relatieve pad werd opgelost ten opzichte van de map van het eerste bestand. Corrigeer het pad, geef --project-directory mee, of verplaats het fragment achter include.
Containers keren terug met nieuwe namen en een volume lijkt leeg. De projectnaam is gewijzigd, omdat de projectnaam de map van het eerste bestand volgt. Voeg een name: op het hoogste niveau toe aan het basisbestand en de naamgeving blijft constant. Het oude volume is nog steeds aanwezig onder het oude voorvoegsel en docker volume ls zal dit tonen.
Een poort die u in de override heeft verwijderd, staat nog steeds open. De ports-samenvoeging heeft toegevoegd in plaats van vervangen. Bevestig dit met docker compose config en gebruik vervolgens !override of verplaats ports uit het basisbestand.
FAQ
Laadt Compose automatisch compose.override.yaml?
Ja, wanneer u docker compose uitvoert zonder de -f-vlag. Compose doorzoekt de werkmap en de bovenliggende mappen naar compose.yaml of docker-compose.yaml. Als er een override-bestand naast staat, wordt dit bestand als tweede geladen. De herkende namen zijn compose.override.yaml, compose.override.yml, docker-compose.override.yml en docker-compose.override.yaml. Het opgeven van een -f schakelt dit uit, waardoor docker compose -f compose.yaml up slechts één bestand leest.
In welke volgorde worden meerdere -f bestanden samengevoegd?
Van links naar rechts. Compose bouwt de configuratie op in de volgorde waarin u de bestanden opgeeft. Elk bestand overschrijft of vult de voorgaande bestanden aan, waardoor het laatste bestand in de reeks bij conflicten voorrang krijgt. Dezelfde lijst moet voor elk commando in dat project worden gebruikt; hiervoor is COMPOSE_FILE=compose.yaml:compose.prod.yaml bedoeld.
Waarom is mijn poort nog steeds gepubliceerd nadat ik deze heb overschreven?
Omdat ports-items worden geïdentificeerd door de volledige set van ip, target, published en protocol. Een override van 127.0.0.1:8080:80 tegenover een basis van 8080:80 verschilt in het ip-gedeelte. Compose behandelt dit daarom als een tweede poort en behoudt beide. Voer docker compose config uit om beide items te zien. Gebruik ports: !override in Compose v2.24.4 of nieuwer, of houd ports uit het basisbestand zodat er niets is om mee samen te voegen.
Wat is het verschil tussen include en -f?
-f legt meerdere bestanden over één applicatie heen, waarbij elk relatief pad in elk bestand wordt opgelost ten opzichte van de map van het eerste bestand. include haalt een afzonderlijke Compose-applicatie binnen, waarbij elk opgenomen pad zijn eigen projectmap behoudt, zodat relatieve paden worden opgelost ten opzichte van de eigen locatie. Gebruik -f voor omgevingslagen van uw eigen stack, en include voor een fragment dat elders wordt beheerd. include vereist Compose v2.20.0 of nieuwer.
Hoe verwijder ik een waarde die in het basisbestand is ingesteld?
Gebruik de !reset-tag in Compose v2.24 of nieuwer. Schrijf ports: !reset [] of MY_VAR: !reset null in het overschrijvende bestand; het attribuut keert dan terug naar de standaardwaarde of naar null. De waarde die u aan de tag geeft is verplicht, maar wordt genegeerd. Als u een attribuut wilt vervangen in plaats van wissen, doet !override dat; dit vereist v2.24.4 of nieuwer.