SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-10-30

Docker Compose wiele plików: jak poprawnie łączyć pliki

Dowiedz się jak Docker Compose scala pliki konfiguracyjne. Wyjaśniamy kolejność łączenia, automatyczne ładowanie compose.override.yaml oraz pułapki przy mapowaniu portów.

Działanie Compose przy użyciu wielu plików

Docker Compose potrafi zbudować jeden projekt w oparciu o kilka plików. Odczytuje je w kolejności otrzymania i scala w jeden model, przy czym późniejszy plik nadpisuje wartości w przypadku konfliktu. Służą do tego dwa mechanizmy wywoływane z linii komend: plik override, który Compose ładuje automatycznie, oraz flaga -f przekazywana ręcznie. Trzeci mechanizm znajduje się wewnątrz samego pliku, jest to element include, który działa w odmienny sposób.

Scalanie nie jest prostym nadpisywaniem. Mapowania są łączone klucz po kluczu, sekwencje są dołączane, a niewielki zestaw pól jest zastępowany w całości. Ta różnica jest źródłem niespodzianek, a lista ports to element, na którym większość użytkowników popełnia błędy.

Wszystkie poniższe informacje dotyczą Compose w wersji v2 lub nowszej, czyli wtyczki docker compose, a nie starego skryptu docker-compose. Obecne wydania mają numerację v5, a wtyczka działa w ten sam sposób. Uruchom docker compose version, aby sprawdzić wersję. Jeśli nie utworzono jeszcze pliku Compose, należy zacząć od przewodnika po podstawach Docker Compose i wrócić tutaj.

Plik override ładowany przez Compose automatycznie

Uruchom docker compose up bez flagi -f, a Compose przeszuka bieżący katalog oraz katalogi nadrzędne w poszukiwaniu compose.yaml lub docker-compose.yaml. Jeśli plik override znajduje się obok pliku bazowego, Compose załaduje go jako drugi, automatycznie.

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

Obecność obu plików jest równoznaczna z ręcznym wskazaniem ich w poleceniu.

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

Nazwy rozpoznawane przez Compose to compose.override.yaml, compose.override.yml oraz starsze docker-compose.override.yml i docker-compose.override.yaml. Każda inna nazwa, na przykład compose.dev.yaml, jest ładowana tylko wtedy, gdy zostanie wskazana za pomocą -f.

W momencie przekazania choćby jednej flagi -f, automatyczne ładowanie zostaje wstrzymane. docker compose -f compose.yaml up odczytuje dokładnie ten jeden plik i ignoruje plik override; na tej właściwości opiera się wzorzec środowisk deweloperskich i produkcyjnych opisany w dalszej części tego przewodnika.

Działa to w obie strony na serwerze. Plik nadpisujący pozostawiony w katalogu wdrożenia jest wczytywany przez każde podstawowe polecenie docker compose uruchomione z tego katalogu, w tym przez polecenie wykonywane przez zadanie cron. W ten sposób stos produkcyjny zaczyna montować za pomocą bind mountu katalog źródłowy, którego nikt nie zamierzał wdrażać. Po każdym wdrożeniu uruchom docker compose config i odczytaj wynik. Gdy wdrożenie jest wykonywane bez nadzoru, kontrola pomaga tylko wtedy, gdy ktoś zostanie poinformowany o błędzie. Służy do tego kanał push, taki jak samodzielnie hostowany serwer ntfy, do którego zadanie cron lub jednostka systemd OnFailure może wysłać powiadomienie.

Kolejność plików z -f oraz rozwiązywanie ścieżek względnych

Compose buduje konfigurację w kolejności podanych plików, przy czym kolejne pliki nadpisują lub uzupełniają poprzednie. W przypadku konfliktów, ostatni plik ma pierwszeństwo.

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

Każde polecenie w ramach projektu wymaga identycznej listy plików. Uruchomienie up z dwoma plikami, a następnie logs z jednym, powoduje odwołanie się do innego scalonego modelu, co jest szybkim sposobem na uzyskanie błędu o nieistniejącej usłudze. Ryzyko rośnie w przypadku stosów, których aktualizacje są uruchamiane jako jednorazowe polecenia, na przykład podczas migracji bazy danych w samodzielnie hostowanym systemie wsparcia Chatwoot, gdzie docker compose run wywołane z błędną listą plików po cichu odnosi się do innego modelu niż ten, z którego korzystają działające usługi. Zamiast tego należy ustawić listę raz, używając zmiennej środowiskowej COMPOSE_FILE.

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

Separator to : w systemie Linux, a COMPOSE_PATH_SEPARATOR pozwala na jego zmianę. Zmienną COMPOSE_FILE można również umieścić w pliku .env projektu, co sprawia, że staje się ona częścią repozytorium, a nie tylko historii powłoki. Wartości ustawione jawnie w wierszu poleceń mają pierwszeństwo przed zmienną środowiskową.

Oto zasada, która wpływa na montowanie wolumenów (bind mounts). W przypadku użycia wielu plików z -f, wszystkie ścieżki względne we wszystkich tych plikach są rozwiązywane względem katalogu pierwszego pliku, a nie pliku, w którym się znajdują. Zapisanie ./data:/var/lib/postgresql/data wewnątrz deploy/prod/compose.prod.yaml spowoduje, że Compose nadal będzie szukać ./data w lokalizacji pliku bazowego. Docker utworzy wówczas pusty katalog w błędnej ścieżce, a kontener uruchomi się bez danych, co wygląda jak ich utrata, choć w rzeczywistości tak nie jest. Należy przekazać --project-directory, aby samodzielnie ustawić ścieżkę bazową, lub użyć include, co powoduje rozwiązywanie ścieżek w każdym pliku względem jego własnego katalogu.

Nazwa projektu pochodzi z tego samego katalogu bazowego, więc zmiana pierwszego pliku na liście może spowodować zmianę nazwy projektu. Zmiana nazwy projektu oznacza nowe nazwy kontenerów i wolumenów, podczas gdy stary wolumen pozostaje na dysku pod starą nazwą. Należy przypiąć nazwę za pomocą parametru najwyższego poziomu name: w pliku bazowym.

name: myapp

Które pola są scalane, a które zastępowane

Compose scala wartości na podstawie typu pola, a nie jego nazwy.

  • Pola jednowartościowe są zastępowane. image, command, entrypoint oraz mem_limit przyjmują późniejszą wartość w całości. Nie można dopisać argumentu do command, ponieważ nadpisanie powoduje zastąpienie całej linii.
  • Mapowania są scalane klucz po kluczu. environment, labels, volumes oraz devices zachowują wszystkie klucze z obu plików, a w przypadku wystąpienia tego samego klucza w obu plikach, pierwszeństwo ma plik późniejszy. Dla environment oraz labels kluczem jest nazwa zmiennej lub etykiety. Dla volumes oraz devices kluczem jest ścieżka w kontenerze.
  • Sekwencje są dołączane. dns, dns_search, expose, tmpfs oraz external_links są łączone. Plik bazowy zawierający expose: ["3000"] scalony z nadpisaniem zawierającym ["4000", "5000"] tworzy ["3000", "4000", "5000"].

Cztery sekwencje posiadają klucz identyfikujący, dzięki czemu wpisy pasujące do tego klucza są scalane zamiast dołączane. volumes, secrets oraz configs są dopasowywane na podstawie target. ports jest dopasowywane na podstawie kombinacji ip, target, published oraz protocol.

Przeczytaj tę zasadę ports dwukrotnie, ponieważ jest to pułapka. Dwa wpisy portów są tym samym wpisem tylko wtedy, gdy wszystkie cztery elementy są zgodne. Zmiana któregokolwiek z nich powoduje, że Compose traktuje je jako dwa niezależne porty i zachowuje oba.

Dlaczego port pozostaje opublikowany mimo nadpisania

Plik bazowy, który publikuje usługę na wszystkich interfejsach:

services:
  web:
    image: nginx:1.27
    ports:
      - "8080:80"

Nadpisanie przygotowane w celu powiązania usługi wyłącznie z localhost, ponieważ przed nią znajduje się reverse proxy:

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

Sprawdź wynik przed założeniem, że konfiguracja zadziałała.

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

Oba wpisy znajdują się w danych wyjściowych. Część ip różni się, 0.0.0.0 kontra 127.0.0.1, więc z punktu widzenia procesu łączenia są to dwa różne porty, a publiczne powiązanie, które próbowano usunąć, nadal istnieje w modelu. Ma to większe znaczenie w Docker niż gdziekolwiek indziej, ponieważ opublikowany port jest zapisywany w iptables przed regułami firewalla. Mechanizm ten opisano w dlaczego opublikowane porty Docker omijają ufw.

Istnieją dwie poprawki. Jawna metoda wykorzystuje znacznik !override, który zastępuje cały atrybut i pomija reguły łączenia:

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

!override wymaga Compose w wersji 2.24.4 lub nowszej. Przenośna poprawka nie wymaga żadnego znacznika: należy całkowicie usunąć ports z pliku bazowego i deklarować go wyłącznie w plikach specyficznych dla danego środowiska. Brak elementów do połączenia oznacza brak wycieku konfiguracji. Jest to wzorzec zastosowany w poniższym przykładzie.

Usuwanie wartości z bazowego zestawu plików

!reset usuwa atrybut, przywracając go do wartości domyślnej lub ustawiając na null. Polecenie wymaga podania wartości, którą następnie ignoruje, dlatego należy wpisać dowolną poprawną, pustą wartość.

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

!reset działa w Compose v2.18.0 lub nowszym, wydanym kilka wersji przed pojawieniem się !override. Należy z niego korzystać, gdy plik bazowy nie podlega edycji, na przykład w przypadku fragmentu dostarczonego przez zewnętrznego dostawcę. Opublikowany stos upstream jest właśnie takim przypadkiem: plik Compose dla samodzielnie hostowanego obszaru roboczego AFFiNE deklaruje cztery kontenery, których nie jesteś autorem, a !reset pozwala wyczyścić jeden atrybut w jednym z nich bez konieczności tworzenia forka pliku i przejmowania obowiązku śledzenia jego zmian.

include, dla stosów złożonych z części

include wczytuje inną aplikację Compose do Twojego modelu. Jest to element najwyższego poziomu, a nie flaga.

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

Każda ścieżka w include jest ładowana jako osobny model aplikacji Compose z własnym katalogiem projektu, więc ścieżki relatywne wewnątrz tego pliku są rozwiązywane względem katalogu tego pliku. To stanowi zasadniczą różnicę względem -f i jest powodem, dla którego include jest właściwym narzędziem, gdy fragment znajduje się w innym folderze lub innym repozytorium. Jest to typowy układ stosu dostawcy, którego nie jesteś autorem: wielousługowy plik Compose dla samodzielnej instalacji Authentik SSO może znajdować się we własnym katalogu z zachowaniem własnych ścieżek relatywnych, podczas gdy Twój plik dotyczy Twoich usług.

Wersja rozszerzona przyjmuje podopcje.

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

path akceptuje listę, a pliki te są scalane zgodnie ze standardowymi regułami, zanim wynik dołączy do Twojego modelu. project_directory ustawia ścieżkę bazową używaną do rozwiązywania ścieżek relatywnych w dołączonym pliku. env_file wskazuje plik, z którego dołączony plik Compose pobiera domyślne wartości interpolacji. Domyślnie jest to .env w katalogu projektu tego pliku, a środowisko Twojego lokalnego projektu nadal ma nad nim pierwszeństwo, co pozwala na nadpisanie jednej z jego zmiennych bez edycji fragmentu. include wymaga Compose w wersji 2.20.0 lub nowszej. Te same opcje sprawdzają się w przypadku dodatku jednokontenerowego do stosu, który już uruchamiasz, na przykład Halcyon, który zmienia wygląd biblioteki Jellyfin na styl wypożyczalni kaset z lat 90.: jego plik zachowuje własny tag obrazu i własne env_file, więc aktualizacja nie oznacza ingerencji w plik, w którym znajduje się Twój stos multimedialny.

Dokumentacja Docker podaje, że nazwa zasobu zadeklarowana zarówno w Twoim pliku, jak i w pliku dołączonym, jest odrzucana jako konflikt. Mechanizm ładowania w bieżącym wydaniu nie działa w ten sposób: według stanu na Compose v5.6.1, zweryfikowanego na podstawie kodu źródłowego ładowarki dostarczanego w dniu 2026-10-27, scala on obie definicje, przy czym Twój plik wygrywa w przypadku każdej sprzecznej wartości, zamiast przerywać działanie z błędem. Nie należy zatem polegać na tym, że zduplikowana nazwa spowoduje błąd. Aby zmienić coś, co deklaruje dołączony plik, należy umieścić zmianę w compose.override.yaml: nadpisanie jest stosowane do złożonego modelu, więc zmiana jest jawna i nie zależy od tego, jak dane wydanie traktuje kolizję. Ten nawyk najbardziej opłaca się w przypadku stosu, którego plik nadrzędny jest nadpisywany przy każdym wydaniu, tak jak w przypadku wielokontenerowych serwerów zdjęć porównywanych w PhotoPrism kontra Immich, gdzie powiązanie z localhost lub dodatkowy wolumen powinny znaleźć się w Twoim nadpisaniu, a nie w pliku, który zostanie zastąpiony przy następnej aktualizacji.

Wersja skrócona: include składa oddzielne aplikacje, -f nakłada konfigurację na jedną aplikację.

Podział na środowisko deweloperskie i produkcyjne na jednym VPS

Poniżej przedstawiono pełny wzorzec w trzech plikach. Plik bazowy deklaruje ustawienia wspólne dla wszystkich środowisk i nie udostępnia żadnych portów.

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:

Warunek depends_on sprawia, że aplikacja oczekuje na bazę danych, która odpowiada na zapytania, a nie tylko na kontener, który istnieje; szczegóły opisano w healthchecks and depends_on conditions. Wartość POSTGRES_PASSWORD jest interpolowana z pliku .env projektu, którego nigdy nie należy umieszczać w git. Zobacz env files and Compose secrets, aby poznać bezpieczniejsze warianty.

Następnie compose.override.yaml, który Compose wczytuje automatycznie. Jest to plik przeznaczony dla programisty.

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"

Na laptopie polecenie docker compose up scala te dwa pliki. Wartość command zastępuje domyślny obraz, ponieważ jest to wartość pojedyncza. Klucz LOG_LEVEL zastępuje info, ponieważ environment scala konfigurację według kluczy. Montowanie typu bind mount oraz dwa opublikowane porty to czyste dodatki, a port bazy danych jest powiązany z localhost, dzięki czemu laptop w sieci współdzielonej nie udostępnia PostgreSQL osobom postronnym.

Na koniec compose.prod.yaml. Jego nazwa nie jest automatycznie wykrywana przez Compose, więc plik nigdy nie zostanie wczytany przypadkowo.

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

Na serwerze VPS należy wskazać oba pliki; samo ich wymienienie sprawia, że plik override jest pomijany.

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

Polecenie ps powinno wykazać, że obie usługi działają, a db powinno wskazywać na (healthy). Ponieważ przekazano -f, plik compose.override.yaml nie został odczytany, więc polecenie deweloperskie, montowanie typu bind mount dla kodu źródłowego oraz publiczny port 3000 nie mają wpływu na środowisko produkcyjne, mimo że plik znajduje się w tym samym katalogu. Port 8000 jest dostępny tylko na localhost, gotowy do obsługi przez proxy: zobacz running several apps behind Traefik, gdy dodasz drugą usługę.

Ustaw COMPOSE_FILE=compose.yaml:compose.prod.yaml w pliku .env na serwerze, a pozostałe polecenia wrócą do postaci zwykłego docker compose logs -f app.

Stos z pojedynczą usługą przyjmuje tę samą strukturę, ponieważ a self-hosted openGym workout tracker musi odpowiadać przez TLS za proxy, zanim zarejestrujesz pierwszy klucz dostępu (passkey), a plik bazowy bez sekcji ports zapobiega sytuacji, w której przypadkowe publiczne powiązanie portu wyprzedzi proxy w obsłudze ruchu.

Przejrzyj scalony model przed wdrożeniem

docker compose config wyświetla w pełni scalony i w pełni zinterpolowany model. Nie jest to podgląd. Jest to dokładna postać danych wejściowych, na których operuje Compose, więc jeśli wynik nie zgadza się z oczekiwaniami, to wynik jest poprawny.

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 pozostawia ${VAR} w formie nierozwiniętej. Użyj tego parametru przed wklejeniem wyniku w jakiekolwiek miejsce, ponieważ standardowe config wypisuje każdy rozwiązany sekret otwartym tekstem. --services wyświetla tylko nazwy usług, co stanowi szybki sposób na potwierdzenie, że include załadowało oczekiwane elementy.

Tryby awarii i ich objawy

no configuration file provided: not found. Narzędzie Compose nie znalazło pliku do odczytu. Użytkownik znajduje się poza katalogiem projektu lub COMPOSE_FILE wskazuje na nieistniejącą ścieżkę. Compose przeszukuje katalogi nadrzędne w poszukiwaniu domyślnego pliku bazowego, jednak nie przeszukuje żadnych lokalizacji w poszukiwaniu pliku wskazanego ręcznie.

WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string. Interpolacja jest rozwiązywana względem pliku .env projektu oraz środowiska powłoki, a katalogiem projektu jest katalog pierwszego pliku -f. Wdrożenie z katalogu innego niż ten, w którym znajduje się .env, powoduje wyświetlenie ostrzeżenia, a następnie odrzucanie wszystkich połączeń przez bazę danych.

Edycja nadpisania nie jest widoczna w docker compose config. Przekazano flagę -f, która wyłącza automatyczne ładowanie nadpisań, lub Compose znalazło compose.yaml w katalogu nadrzędnym, a plik nadpisania nie znajduje się w tym samym miejscu. Uruchomienie docker compose config bez dodatkowych argumentów pozwala sprawdzić, jaki model jest faktycznie budowany przez Compose.

Montowanie typu bind jest puste, a Docker utworzył niepożądany katalog. Ścieżka względna została rozwiązana względem katalogu pierwszego pliku. Należy poprawić ścieżkę, przekazać --project-directory lub przenieść fragment za include.

Kontenery wracają z nowymi nazwami, a wolumen wydaje się pusty. Nazwa projektu uległa zmianie, ponieważ jest ona powiązana z katalogiem pierwszego pliku. Dodanie parametru name: najwyższego poziomu do pliku bazowego ustabilizuje nazewnictwo. Stary wolumen nadal istnieje pod poprzednim prefiksem i można go wyświetlić za pomocą docker volume ls.

Port usunięty w nadpisaniu pozostaje otwarty. Scalanie ports spowodowało dołączenie zamiast zastąpienia. Należy potwierdzić konfigurację za pomocą docker compose config, a następnie użyć !override lub przenieść ports poza plik bazowy.

FAQ

Czy Compose automatycznie wczytuje compose.override.yaml?

Tak, jeśli uruchomisz docker compose bez flagi -f. Compose przeszukuje katalog roboczy oraz katalogi nadrzędne w poszukiwaniu compose.yaml lub docker-compose.yaml. Jeśli w tym samym miejscu znajduje się plik nadpisujący, zostanie on wczytany jako drugi. Rozpoznawane nazwy to compose.override.yaml, compose.override.yml, docker-compose.override.yml oraz docker-compose.override.yaml. Przekazanie dowolnego -f wyłącza ten mechanizm, dlatego docker compose -f compose.yaml up odczytuje tylko jeden plik.

W jakiej kolejności scalane są pliki przekazane przez -f?

Od lewej do prawej. Compose buduje konfigurację w kolejności podania plików, a każdy kolejny plik nadpisuje lub uzupełnia poprzednie. Oznacza to, że ostatni plik w linii rozstrzyga wszelkie konflikty. Ta sama lista musi być używana dla każdego polecenia w danym projekcie, do czego służy COMPOSE_FILE=compose.yaml:compose.prod.yaml.

Dlaczego port jest nadal publikowany po jego nadpisaniu?

Ponieważ wpisy ports są identyfikowane przez cały zestaw ip, target, published oraz protocol. Nadpisanie 127.0.0.1:8080:80 względem bazy 8080:80 różni się w części ip, więc Compose traktuje to jako drugi port i zachowuje oba. Uruchom docker compose config, aby zobaczyć oba wpisy. Użyj ports: !override w Compose v2.24.4 lub nowszym albo usuń ports z pliku bazowego, aby nie było czego scalać.

Jaka jest różnica między include a -f?

-f nakłada kilka plików na jedną aplikację, a każda ścieżka względna w każdym pliku jest rozwiązywana względem katalogu pierwszego pliku. include wczytuje oddzielną aplikację Compose, a każda dołączona ścieżka zachowuje własny katalog projektu, więc ścieżki względne są rozwiązywane względem niej samej. Użyj -f dla warstw środowiskowych własnego stosu, a include dla fragmentu utrzymywanego w innym miejscu. include wymaga Compose v2.20.0 lub nowszego.

Jak usunąć wartość ustawioną w pliku bazowym?

Użyj znacznika !reset w Compose v2.18.0 lub nowszym. Wpisz ports: !reset [] lub MY_VAR: !reset null w pliku nadpisującym, a atrybut powróci do wartości domyślnej lub zostanie ustawiony na null. Wartość przypisana do znacznika jest wymagana, ale ignorowana. Jeśli chcesz zastąpić atrybut zamiast go usuwać, służy do tego !override, co wymaga wersji v2.24.4 lub nowszej.

Czy ten artykuł był przydatny?