SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-07

Docker Compose: .env, env_file i environment – różnice

Wyjaśnienie różnic między plikami .env, atrybutem env_file oraz sekcją environment w Docker Compose. Sprawdź kolejność pierwszeństwa zmiennych i dowiedz się, gdzie przechowywać sekrety.

Trzy znaczenia terminu plik env

Docker Compose posiada trzy odrębne mechanizmy o myląco podobnych nazwach. Plik .env wypełnia symbole zastępcze ${VARIABLE} wewnątrz samego compose.yaml, zanim Compose w ogóle przetworzy plik. Atrybut env_file: wczytuje plik z parami klucz/wartość do środowiska kontenera. Atrybut environment: ustawia zmienne bezpośrednio w kontenerze, zapisane w pliku compose. Nie są one zamienne, a w przypadku ustawienia tego samego klucza przez dwa z nich, zwycięzcę określa udokumentowana kolejność pierwszeństwa.

Niniejszy przewodnik pokazuje działanie każdego z nich, potwierdza pierwszeństwo za pomocą polecenia, które można uruchomić, a następnie omawia kwestię o większym znaczeniu: zmienne środowiskowe są czytelne dla każdego, kto może uruchomić docker inspect, dlatego hasła nie powinny być w nich przechowywane. Jeśli użytkownik dopiero zaczyna pracę z plikami compose, należy zacząć od Podstawy Docker Compose na VPS, a następnie wrócić tutaj w celu konfiguracji.

Plik .env służy do konfiguracji compose, a nie kontenera

Utwórz katalog i umieść w nim dwa pliki.

mkdir -p ~/envdemo && cd ~/envdemo
printf 'ALPINE_TAG=3.20\n' > .env
services:
  demo:
    image: alpine:${ALPINE_TAG}
    command: printenv ALPINE_TAG

Teraz sprawdź, co faktycznie zinterpretowało Compose.

docker compose config

Dane wyjściowe pokazują image: alpine:3.20. Symbol zastępczy zniknął, ponieważ interpolacja nastąpiła w momencie parsowania. Compose szuka .env w katalogu projektu, czyli w katalogu zawierającym plik compose, i podstawia każdą znalezioną wartość ${NAME}.

Następnie uruchom usługę.

docker compose run --rm demo

printenv ALPINE_TAG kończy działanie ze statusem 1 i nie wyświetla niczego. Zmienna nie istnieje wewnątrz kontenera. Jest to najczęstsze nieporozumienie: .env skonfigurowało plik compose, a nie proces. Plik .env zawierający POSTGRES_PASSWORD=hunter2 nie robi absolutnie nic dla bazy danych, chyba że jakaś część pliku compose się do niego odwołuje.

${NAME:-default} zapewnia wartość domyślną, gdy zmienna jest nieustawiona lub pusta. ${NAME:?message} sprawia, że Compose odmawia uruchomienia i wyświetla komunikat, co jest właściwym wyborem dla wartości, która nie posiada bezpiecznego ustawienia domyślnego.

env_file ładuje zmienne do kontenera

Atrybut env_file: wskazuje jeden lub więcej plików, których zawartość staje się zmiennymi środowiskowymi kontenera.

printf 'GREETING=from_env_file\nAPP_MODE=production\n' > app.env
services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
docker compose run --rm demo

To polecenie wyświetla from_env_file. Format pliku to zwykłe linie KEY=value, po jednej w linii, gdzie # rozpoczyna komentarz. Nie jest to powłoka shell. Cudzysłowy są w większości przypadków zachowywane jako część wartości, a przedrostki export nie są wymagane. Nie należy umieszczać spacji wokół znaku =, ponieważ KEY = value tworzy zmienną o nazwie dosłownie KEY z wiodącą spacją w wartości.

Brak ścieżki env_file powoduje błąd i zatrzymanie działania Compose. Należy oznaczyć plik jako opcjonalny, jeśli jego brak jest dopuszczalny:

    env_file:
      - path: ./app.env
        required: false

ustawianie zmiennych środowiskowych w linii

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    environment:
      GREETING: from_environment

Akceptowane są dwie składnie: powyższa forma mapowania oraz forma listy z użyciem - GREETING=from_environment. Działają one identycznie. Forma listy posiada dodatkową funkcję: klucz bez przypisanej wartości przekazuje zmienną z powłoki, w której uruchomiono docker compose.

    environment:
      - GREETING
GREETING=from_my_shell docker compose run --rm demo

Polecenie to wypisuje from_my_shell. Uruchomienie go bez ustawienia GREETING w powłoce powoduje, że Compose nie ustawia żadnej wartości, nie generując przy tym ostrzeżenia. Warto pamiętać o tym cichym niepowodzeniu przekazywania, ponieważ usługa startująca z pustą zmienną hasła często uruchamia się poprawnie, pozostając całkowicie niezabezpieczoną.

Która wartość ma pierwszeństwo

Dokumentacja Docker określa kolejność pierwszeństwa, od najwyższego: docker compose run -e w wierszu poleceń, następnie environment lub env_file, których wartość jest interpolowana z powłoki lub pliku env, potem zwykłe environment w pliku compose, następnie env_file, a na końcu dyrektywa ENV wbudowana w obraz.

W skrócie do codziennej pracy: environment: ma pierwszeństwo przed env_file:, a -e w wierszu poleceń wygrywa z obydwoma. Można to sprawdzić w jednym pliku.

services:
  demo:
    image: alpine:3.20
    command: printenv GREETING
    env_file:
      - ./app.env
    environment:
      GREETING: from_environment
docker compose run --rm demo
docker compose run --rm -e GREETING=from_cli demo printenv GREETING

Pierwsze polecenie wypisuje from_environment, co oznacza, że environment: nadpisało wartość w app.env. Drugie wypisuje from_cli. Żaden element w pliku compose nie nadpisuje wiersza poleceń.

Jeśli kontener zachowuje się tak, jakby konfiguracja nie została zastosowana, nie należy zgadywać. docker compose config wyświetla w pełni rozwiązaną postać pliku, a docker compose config --environment drukuje zmienne interpolacji, z których korzysta Compose. Większość zgłoszeń typu "mój plik env jest ignorowany" wynika z ustawienia wartości dwukrotnie na różnych poziomach.

Dlaczego zmienne środowiskowe wyciekają

Ustawienie hasła w environment: powoduje jego zapisanie w konfiguracji kontenera na dysku, co czyni je widocznym dla każdego użytkownika w grupie docker.

docker compose run -d --name leaky -e DB_PASSWORD=hunter2 demo sleep 300
docker inspect leaky --format '{{json .Config.Env}}'

Dane wyjściowe zawierają "DB_PASSWORD=hunter2" w postaci jawnego tekstu. Trzy kolejne ścieżki ujawniają tę samą wartość. docker compose config wypisuje ją w terminalu, co prowadzi do jej wklejenia na forum wsparcia technicznego. Każdy proces wewnątrz kontenera może odczytać /proc/1/environ, a każdy proces potomny dziedziczy tę zmienną. Ponadto procedury obsługi awarii aplikacji rutynowo zrzucają całe środowisko do dziennika lub raportu o błędach.

Członkostwo w grupie docker jest równoznaczne z posiadaniem uprawnień root na hoście, więc nie jest to granica uprawnień, na której można polegać. Przewodnik dotyczący kont użytkowników o najniższych wymaganych uprawnieniach na VPS wyjaśnia, dlaczego warto ograniczyć dostęp do tej grupy na każdym współdzielonym serwerze.

Compose secrets przechowuje wartość w pliku

Compose obsługuje sekrety oparte na plikach. Wartość jest montowana w kontenerze jako plik, zamiast być wstrzykiwana do zmiennych środowiskowych.

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt

Sekret jest montowany w /run/secrets/db_password wewnątrz kontenera. Nazwa po ukośniku to nazwa sekretu z głównego bloku secrets:.

Sufiks _FILE to konwencja stosowana przez oficjalne obrazy Docker, w tym postgres, mysql oraz mariadb. Skrypty punktu wejścia (entrypoint) sprawdzają istnienie VARNAME_FILE, odczytują plik i wykorzystują jego zawartość. Nie jest to funkcja samego Docker, więc działa tylko wtedy, gdy obraz ją implementuje. Przed założeniem, że SOMETHING_FILE zostanie obsłużone, należy sprawdzić dokumentację obrazu. Aplikacje, które tego nie wspierają, często mogą samodzielnie odczytać plik podczas uruchamiania, albo można przekazać ścieżkę i obsłużyć to we własnym skrypcie entrypoint.

Weryfikacja z wnętrza uruchomionego kontenera:

docker compose exec db cat /run/secrets/db_password
docker compose exec db printenv POSTGRES_PASSWORD

Pierwsze polecenie wyświetla hasło. Drugie nie wyświetla nic, ponieważ wartość nigdy nie trafiła do środowiska. Na tym polega główny cel: docker inspect dla tego kontenera pokazuje jedynie nieszkodliwą ścieżkę.

Należy chronić plik źródłowy na hoście, ponieważ sekret jest tak prywatny, jak plik, w którym się znajduje:

chmod 600 db_password.txt

Pragmatyczne podejście na serwerze VPS

Wiele obrazów przeznaczonych do samodzielnego hostowania nie obsługuje zmiennych _FILE, dlatego zmienne środowiskowe pozostają jedynym sposobem przekazywania danych. Na serwerze VPS zarządzanym przez jednego administratora realistycznym celem jest uniemożliwienie odczytu tych wartości przez dowolnego użytkownika systemu oraz wykluczenie ich z repozytorium git.

sudo install -o root -g root -m 600 /dev/null /etc/myapp/app.env
sudo nano /etc/myapp/app.env
    env_file:
      - /etc/myapp/app.env

Polecenie install -m 600 tworzy plik z odpowiednimi uprawnieniami od razu w momencie zapisu, co eliminuje ryzyko odczytu przez osoby niepowołane. Właścicielem pliku jest root, więc użytkownik bez uprawnień roota nie może go odczytać, choć każdy, kto może wykonać docker, nadal jest w stanie odczytać wartość z poziomu kontenera. Należy dodać *.env oraz .env do pliku .gitignore, a do repozytorium zatwierdzić jedynie plik app.env.example zawierający nazwy kluczy z pustymi wartościami. Zatwierdzone w repozytorium hasło należy uznać za skompromitowane i wymagające rotacji.

Rotacja wartości wymaga restartu usługi. Zmienne środowiskowe są odczytywane jednorazowo podczas uruchamiania procesu kontenera, więc edycja pliku nie przynosi efektu do momentu wykonania docker compose up -d --force-recreate db. Jest to ten sam schemat, który zastosowano w przewodniku n8n za HTTPS na VPS, gdzie klucz szyfrujący znajduje się poza plikiem compose.

Podział konfiguracji w zależności od środowiska

Domyślnie Compose odczytuje .env z katalogu projektu. Wskaż inną lokalizację za pomocą --env-file.

docker compose --env-file .env.staging config

Wiele plików jest odczytywanych w określonej kolejności, przy czym pliki wczytane później nadpisują te wcześniejsze. Przechowuj domyślne ustawienia niebędące danymi wrażliwymi w pliku podlegającym kontroli wersji, a sekrety w pliku, który nigdy nie opuszcza serwera. Ta sama zasada dotyczy env_file:, gdzie w przypadku zduplikowanego klucza pierwszeństwo ma ostatni wymieniony plik.

FAQ

Dlaczego mój plik .env jest ignorowany wewnątrz kontenera?

Nie jest ignorowany. Plik .env służy wyłącznie do podstawiania wartości w miejsce symboli zastępczych ${NAME} w pliku compose. Nigdy nie ustawia on zmiennych bezpośrednio wewnątrz kontenera. Aby przekazać wartość do kontenera, należy odwołać się do niej za pomocą environment: { KEY: "${NAME}" } lub użyć env_file: ./that-file.env.

Co ma wyższy priorytet: environment czy env_file?

environment: ma wyższy priorytet. Zgodnie z dokumentacją Docker, atrybut environment jest nadrzędny wobec env_file, a oba są niżej w hierarchii niż docker compose run -e przekazywane w linii poleceń. Jeśli klucz jest zdefiniowany w obu miejscach, wartość w env_file jest pomijana bez żadnego komunikatu.

Jak sprawdzić ostateczną wartość, której użyje Compose?

Należy wykonać docker compose config, aby wyświetlić w pełni przetworzony plik compose z uwzględnieniem wszystkich interpolacji. W przypadku uruchomionego kontenera, polecenie docker inspect <container> --format '{{json .Config.Env}}' pokazuje dokładnie to, co otrzymał jego proces.

Czy sekrety w Compose są szyfrowane?

Nie. Sekret oparty na pliku jest montowany w kontenerze jako zwykły plik w lokalizacji /run/secrets/<name>, a plik źródłowy znajduje się na dysku hosta w postaci niezaszyfrowanej. Korzyścią jest ograniczenie zakresu, a nie szyfrowanie: wartość nie trafia do środowiska kontenera, nie pojawia się w danych wyjściowych docker inspect oraz nie jest obecna w zrzutach pamięci po awarii, które zawierają zmienne środowiskowe.

Czy w pliku env można używać cudzysłowów i spacji?

Należy użyć KEY=value with spaces i pominąć cudzysłowy. Compose traktuje całą resztę linii jako wartość, więc cudzysłowy zazwyczaj stają się dosłownymi znakami wewnątrz tej wartości. Nigdy nie należy dodawać spacji wokół =, ponieważ klucz otrzyma wtedy spację na końcu i żadne dopasowanie nie zadziała.