SSD Nodes Learn 8GB RAM — $66/jaar
Gidsen Matt ConnorDoor Matt Connor · Bijgewerkt 2026-08-02

Docker Compose met meerdere bestanden samenvoegen

Ontdek hoe compose.override.yaml zelfstandig wordt geladen, waarom de bestandsvolgorde uitmaakt, hoe ports een poort openhoudt en hoe include dev en prod splitst.

Wat Compose doet met meer dan één bestand

Docker Compose kan één project opbouwen uit meerdere bestanden. Het leest deze in de volgorde waarin het ze ontvangt en voegt ze samen tot één model. Een later bestand heeft daarbij voorrang bij elke conflicterende waarde. Dit kan vanaf de opdrachtregel op twee manieren: met een overridebestand dat Compose automatisch laadt en met de -f-vlag die u handmatig opgeeft. Een derde manier staat in het bestand zelf, in het element include. Deze werkt anders dan de andere twee.

Samenvoegen betekent niet dat waarden simpelweg worden overschreven. Mappings worden sleutel voor sleutel samengevoegd, reeksen worden aangevuld en een beperkte set velden wordt volledig vervangen. Juist dit verschil veroorzaakt verrassingen. De lijst ports zorgt daarbij bij vrijwel iedereen voor problemen.

Hieronder wordt uitgegaan van Compose v2 en de docker compose-plugin, niet van het oude docker-compose-script. Voer docker compose version uit om dit te controleren. Als u nog geen Compose-bestand hebt geschreven, begint u met de basisgids voor Docker Compose en keert u daarna hier terug.

Het override-bestand dat Compose zonder expliciete opdracht laadt

Voer docker compose up uit zonder de vlag -f. Compose zoekt dan in de werkdirectory en vervolgens in de bovenliggende mappen naar compose.yaml of docker-compose.yaml. Als er naast het basisbestand een override-bestand staat, laadt Compose dat automatisch daarna.

ls compose.yaml compose.override.yaml
docker compose up -d

Als beide bestanden aanwezig zijn, is dat hetzelfde als wanneer u ze handmatig opgeeft.

docker compose -f compose.yaml -f compose.override.yaml up -d

De 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 als u die met -f opgeeft.

Zodra u één -f opgeeft, stopt het automatisch laden. docker compose -f compose.yaml up leest dan precies dat ene bestand en negeert het override-bestand. Het dev- en prod-patroon verderop in deze handleiding is hierop gebaseerd.

Op een server kan dit ook ongewenste gevolgen hebben. Een override-bestand in de deploydirectory wordt geladen door elke kale opdracht docker compose die vanuit die directory wordt uitgevoerd, ook door de opdracht die uw cron-job uitvoert. Daardoor kan een productiestack uiteindelijk een bronmap als bind-mount koppelen terwijl niemand die had willen deployen. Voer na elke deploy docker compose config uit en controleer de uitvoer.

Volgorde met -f en waar relatieve paden worden opgelost

Compose bouwt de configuratie op in de volgorde waarin u de bestanden opgeeft. Latere bestanden overschrijven eerdere bestanden en voegen er instellingen aan toe. Van links naar rechts geldt: de laatste instelling heeft voorrang.

docker compose -f compose.yaml -f compose.prod.yaml config
docker compose -f compose.yaml -f compose.prod.yaml up -d

Elke opdracht in dat project heeft dezelfde bestandslijst nodig. Als u up met twee bestanden uitvoert en logs met één bestand, gebruikt u een ander samengevoegd model. Daardoor kan Compose aangeven dat een service niet bestaat. Stel de lijst daarom één keer in met de omgevingsvariabele COMPOSE_FILE.

export COMPOSE_FILE=compose.yaml:compose.prod.yaml
docker compose config
docker compose up -d

Het scheidingsteken is : op Linux. Met COMPOSE_PATH_SEPARATOR wijzigt u dit. COMPOSE_FILE kan ook in het projectbestand .env staan. Daardoor maakt u de instelling onderdeel van de checkout in plaats van uw shellgeschiedenis. Een instelling die u expliciet op de opdrachtregel opgeeft, heeft voorrang op de omgevingsvariabele.

Hier volgt de regel die bind mounts kan verstoren. Wanneer u meerdere bestanden gebruikt met -f, worden alle relatieve paden in al deze bestanden opgelost ten opzichte van de map van het eerste bestand. Ze worden dus niet opgelost ten opzichte van het bestand waarin ze staan. Schrijf ./data:/var/lib/postgresql/data in deploy/prod/compose.prod.yaml, dan zoekt Compose nog steeds naar ./data naast het basisbestand. Docker maakt vervolgens een lege map op dat verkeerde pad. De container start dan zonder inhoud in die map. Dit lijkt op gegevensverlies, maar dat is het niet. Geef --project-directory door om het basispad zelf in te stellen. U kunt ook include gebruiken. Daarmee wordt elk bestand opgelost ten opzichte van de eigen map.

De projectnaam is afkomstig uit dezelfde basismap. Als u wijzigt welk bestand het eerste is, kan de projectnaam dus veranderen. Een gewijzigde projectnaam leidt tot nieuwe container- en volumenamen. Het oude volume staat nog op schijf onder de oude naam. Leg de naam daarom vast met een top-level name: in het basisbestand.

name: myapp

Welke velden worden samengevoegd en welke worden vervangen

Compose voegt waarden samen op basis van het type van de waarde, niet op basis van de veldnaam.

  • Velden met één waarde worden vervangen. image, command, entrypoint en mem_limit nemen zonder meer de latere waarde over. U kunt geen argument aan een command toevoegen, omdat de overschrijving de volledige regel vervangt.
  • Toewijzingen worden sleutel voor sleutel samengevoegd. environment, labels, volumes en devices behouden elke sleutel uit beide bestanden. Het latere bestand heeft voorrang bij sleutels die in beide bestanden voorkomen. Voor environment en labels is de sleutel de naam van de variabele of het label. Voor volumes en devices is de sleutel het containerpad.
  • Reeksen worden achter elkaar geplaatst. dns, dns_search, expose, tmpfs en external_links worden samengevoegd. Een basisconfiguratie met expose: ["3000"] die wordt samengevoegd met een overschrijving met ["4000", "5000"], levert ["3000", "4000", "5000"] op.

Vier reeksen hebben een identiteitssleutel. Daardoor worden items met dezelfde sleutel samengevoegd in plaats van achter elkaar geplaatst. volumes, secrets en configs worden gekoppeld op basis van target. ports wordt gekoppeld op basis van de combinatie van ip, target, published en protocol.

Lees die regel voor ports tweemaal, want daar gaat het vaak mis. Twee poortitems zijn alleen hetzelfde item als alle vier onderdelen overeenkomen. Als u een van deze onderdelen wijzigt, ziet Compose een tweede, niet-gerelateerd poortitem. Daarom behoudt het beide items.

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 de service alleen aan localhost bindt, omdat er een reverse proxy voor komt te staan:

services:
  web:
    ports:
      - "127.0.0.1:8080:80"

Controleer het resultaat voordat u ervan uitgaat dat het is gelukt.

docker compose -f compose.yaml -f compose.prod.yaml config

Beide vermeldingen staan in de uitvoer. Het deel ip verschilt, 0.0.0.0 tegenover 127.0.0.1. Voor de samenvoeging zijn het daarom twee verschillende poorten en de openbare binding die u probeerde te verwijderen, staat nog steeds in het model. Dit is belangrijker bij Docker dan elders, omdat een gepubliceerde poort vóór uw firewallregels in iptables wordt geschreven. Het mechanisme wordt beschreven in waarom gepubliceerde Docker-poorten ufw omzeilen.

Er zijn twee oplossingen. De expliciete oplossing is de tag !override. Deze vervangt het volledige attribuut en slaat de samenvoegingsregels over:

services:
  web:
    ports: !override
      - "127.0.0.1:8080:80"

Voor !override is Compose v2.24.4 of nieuwer vereist. De draagbare oplossing heeft geen tag nodig: laat ports volledig weg uit het basisbestand en declareer het alleen in de omgevingsspecifieke bestanden. Als er niets hoeft te worden samengevoegd, kan er ook niets uitlekken. Dat is het patroon in het uitgewerkte voorbeeld hieronder.

Een waarde uit de basisbestandenset verwijderen

!reset verwijdert een attribuut en zet het terug op de standaardwaarde of op null. De opdracht vereist een waarde, maar negeert deze. Geef daarom een geldige lege waarde op.

services:
  web:
    ports: !reset []
    environment:
      DEBUG: !reset null

!reset vereist Compose v2.24 of nieuwer. Gebruik deze opdracht wanneer u het basisbestand niet kunt bewerken, bijvoorbeeld een fragment van een leverancier dat u inlaadt.

include, voor stacks die uit onderdelen zijn samengesteld

include voegt een andere Compose-toepassing toe aan uw model. Dit is een element op het hoogste niveau en geen vlag.

include:
  - path: ../commons/compose.yaml

Elk pad in include wordt geladen als een eigen Compose-toepassingsmodel, met een eigen projectmap. Relatieve paden in dat bestand worden daardoor opgelost ten opzichte van de map van dat bestand. Dat is het werkelijke verschil met -f. Daarom is include het juiste hulpmiddel wanneer het fragment zich in een andere map of repository bevindt.

De lange vorm accepteert subopties.

include:
  - path:
      - ../monitoring/compose.yaml
      - ../monitoring/compose.vps.yaml
    project_directory: ../monitoring
    env_file: ../monitoring/.env

path accepteert een lijst. Deze bestanden worden volgens de normale regels samengevoegd voordat het resultaat aan uw model wordt toegevoegd. project_directory stelt het basispad in waarmee relatieve paden in het opgenomen bestand worden opgelost. env_file geeft het opgenomen bestand eigen variabelen voor interpolatie. Daardoor leest een gedeeld fragment niet ongemerkt de .env van uw project. Voor include is Compose v2.20.0 of nieuwer vereist.

Dubbele resourcenamen in uw bestand en een opgenomen bestand worden als fout gemeld in plaats van stilzwijgend samengevoegd. Dat is opzettelijk. Als u iets wilt wijzigen dat in een opgenomen bestand is gedeclareerd, zet u de wijziging in compose.override.yaml. De override wordt toegepast op het samengestelde model. Daardoor kan deze opgenomen resources wijzigen zonder ermee te conflicteren.

Kort gezegd: include stelt afzonderlijke toepassingen samen; -f voegt configuratie toe aan één toepassing.

Een scheiding tussen development en productie op één VPS

Hier is het volledige patroon in drie bestanden. Het basisbestand declareert wat overal geldt en publiceert helemaal geen poorten.

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 voorwaarde depends_on zorgt ervoor dat de app wacht op een database die antwoord geeft, en niet alleen op een container die bestaat. Dit wordt uitgelegd in healthchecks en depends_on-voorwaarden. POSTGRES_PASSWORD wordt geïnterpoleerd uit het projectbestand .env, dat nooit in git mag staan. Zie env-bestanden en Compose-secrets voor veiligere varianten.

Vervolgens compose.override.yaml, dat Compose automatisch laadt. Dit is het bestand voor development.

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 standaardwaarde van de image omdat deze waarde enkelvoudig is. LOG_LEVEL vervangt info omdat environment samenvoegt op basis van de sleutel. De bind mount en de twee gepubliceerde poorten worden alleen toegevoegd. De databasepoort is aan localhost gebonden, zodat een laptop op een gedeeld netwerk PostgreSQL niet naar andere gebruikers aanbiedt.

Ten slotte compose.prod.yaml. De naam ervan is geen naam waarnaar Compose zoekt. Het bestand wordt daarom nooit per ongeluk geladen.

services:
  app:
    ports:
      - "127.0.0.1:8000:3000"
    deploy:
      resources:
        limits:
          memory: 512M

Op de VPS geeft u beide bestanden op. Door ze expliciet op te geven, sluit u de override uit.

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 ps

ps moet beide services als actief tonen, waarbij db (healthy) toont. Omdat u -f hebt opgegeven, is compose.override.yaml niet ingelezen. Daardoor kunnen het development-commando, de source bind mount en de openbare poort 3000 de productieomgeving niet bereiken, ook al staat het bestand in dezelfde directory. Poort 8000 is alleen beschikbaar op localhost en kan door een proxy worden gebruikt. Zie meerdere apps achter Traefik uitvoeren wanneer u de tweede service toevoegt.

Stel COMPOSE_FILE=compose.yaml:compose.prod.yaml in in het .env van de server. Daarna kunt u uw overige commando's weer als gewone docker compose logs -f app uitvoeren.

Lees het samengevoegde model voordat u implementeert

docker compose config geeft het volledig samengevoegde en geïnterpoleerde model weer. Het is geen voorbeeldweergave. Dit is exact de invoer waarop Compose actie onderneemt. Als de uitvoer niet overeenkomt met 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} onverwerkt. Gebruik dit voordat u de uitvoer ergens plakt, omdat plain config elk opgelost geheim als tekst zonder versleuteling weergeeft. --services geeft alleen de servicenamen weer. Zo kunt u snel controleren of een include het verwachte heeft ingeladen.

Storingsscenario's en wat u ziet

no configuration file provided: not found. Compose vond niets om te lezen. U bevindt zich buiten de projectmap, of COMPOSE_FILE verwijst naar een pad dat niet bestaat. Compose doorzoekt bovenliggende mappen voor het standaardbasisbestand, maar zoekt nergens naar een bestand dat u zelf hebt opgegeven.

WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. Interpolatie wordt uitgevoerd op basis van het projectbestand .env en de shellomgeving. De projectmap is hier de map van het eerste -f-bestand. Als u vanuit een andere map implementeert dan de map waarin .env staat, krijgt u deze waarschuwing en vervolgens een database die elke verbinding weigert.

Uw wijziging in het override-bestand wordt niet weergegeven in docker compose config. U hebt mogelijk -f doorgegeven, waardoor automatisch laden van override-bestanden wordt uitgeschakeld. Of Compose heeft compose.yaml in een bovenliggende map gevonden en uw override-bestand staat niet naast dat bestand. Als u docker compose config zonder andere argumenten uitvoert, ziet u welk model Compose daadwerkelijk bouwt.

Een bind-mount is leeg en Docker heeft een map gemaakt waar u niet om hebt gevraagd. Het relatieve pad is geïnterpreteerd ten opzichte van de map van het eerste bestand. Corrigeer het pad, geef --project-directory door of plaats het fragment achter include.

Containers krijgen nieuwe namen en een volume lijkt leeg. De projectnaam is gewijzigd, omdat de projectnaam de map van het eerste bestand volgt. Voeg op het hoogste niveau name: toe aan het basisbestand. Daarna verandert de naamgeving niet meer. Het oude volume staat nog onder het oude voorvoegsel. Met docker volume ls kunt u dit controleren.

Een poort die u in het override-bestand hebt verwijderd, staat nog steeds open. De samenvoeging met ports heeft de configuratie toegevoegd in plaats van vervangen. Controleer dit met docker compose config. Gebruik vervolgens !override of verplaats ports uit het basisbestand.

FAQ

Laadt Compose compose.override.yaml automatisch?

Ja, wanneer u docker compose uitvoert zonder de vlag -f. Compose zoekt in de werkmap en de bovenliggende mappen naar compose.yaml of docker-compose.yaml. Als er een override-bestand naast staat, wordt dat bestand als tweede geladen. De herkende namen zijn compose.override.yaml, compose.override.yml, docker-compose.override.yml en docker-compose.override.yaml. Met een -f wordt dit uitgeschakeld, zodat 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 waarden uit eerdere bestanden en voegt nieuwe waarden toe. Bij conflicten heeft het laatste bestand op de opdrachtregel dus voorrang. Voor elke opdracht in dat project moet u dezelfde lijst gebruiken. Daarvoor dient COMPOSE_FILE=compose.yaml:compose.prod.yaml.

Waarom wordt mijn poort nog steeds gepubliceerd nadat ik deze heb overschreven?

Omdat vermeldingen van ports worden geïdentificeerd aan de hand van de volledige combinatie van ip, target, published en protocol. Een override van 127.0.0.1:8080:80 ten opzichte van een basiswaarde van 8080:80 verschilt in het onderdeel ip. Compose behandelt dit daarom als een tweede poort en behoudt beide vermeldingen. Voer docker compose config uit om de twee vermeldingen te zien. Gebruik ports: !override met Compose v2.24.4 of nieuwer. U kunt ports ook uit het basisbestand verwijderen, zodat er niets is om mee samen te voegen.

Wat is het verschil tussen include en -f?

-f voegt meerdere bestanden samen tot één applicatie. Relatieve paden in alle bestanden worden opgelost ten opzichte van de map van het eerste bestand. include voegt een afzonderlijke Compose-applicatie toe. Elk opgenomen pad behoudt daarbij zijn eigen projectmap, zodat relatieve paden ten opzichte van die map worden opgelost. Gebruik -f voor omgevingslagen van uw eigen stack en include voor een fragment dat elders wordt onderhouden. include vereist Compose v2.20.0 of nieuwer.

Hoe verwijder ik een waarde die in het basisbestand is ingesteld?

Gebruik de tag !reset met Compose v2.24 of nieuwer. Schrijf ports: !reset [] of MY_VAR: !reset null in het override-bestand. Het attribuut krijgt dan de standaardwaarde of de waarde null. De waarde die u aan de tag meegeeft, is verplicht maar wordt genegeerd. Als u een attribuut wilt vervangen in plaats van wissen, doet !override dat. Hiervoor is v2.24.4 of nieuwer vereist.