SSD Nodes Learn 8GB RAM — $66/rok
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-01

Docker Compose: .env, env_file i environment

Poznaj różnice między .env, env_file i environment w Docker Compose, sprawdź kolejność pierwszeństwa oraz dowiedz się, dlaczego haseł nie należy przechowywać w zmiennych.

Trzy elementy nazywane plikiem env

Docker Compose udostępnia trzy odrębne mechanizmy o myląco podobnych nazwach. Plik .env zastępuje symbole zastępcze ${VARIABLE} bezpośrednio w pliku compose.yaml, zanim Compose go przeanalizuje. Atrybut env_file: ładuje pary klucz/wartość do środowiska kontenera. Atrybut environment: ustawia zmienne bezpośrednio w kontenerze, w pliku Compose. Mechanizmy te nie są zamienne. Jeśli dwa z nich ustawiają ten sam klucz, obowiązuje ustalona, udokumentowana kolejność pierwszeństwa.

W tym przewodniku przedstawiono działanie każdego mechanizmu, potwierdzono pierwszeństwo za pomocą polecenia, które można uruchomić, a następnie omówiono ważniejszą kwestię: zmienne środowiskowe może odczytać każdy, kto może uruchomić docker inspect, dlatego haseł nie należy w nich przechowywać. Jeśli pliki Compose nie są jeszcze znane, należy rozpocząć od Podstawy Docker Compose na VPS i wrócić tutaj, aby zapoznać się z konfiguracją.

Plik .env służy do pliku Compose, a nie do 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

Sprawdź teraz, co faktycznie przeanalizował Compose.

docker compose config

Dane wyjściowe pokazują image: alpine:3.20. Symbol zastępczy zniknął, ponieważ interpolacja nastąpiła podczas analizowania pliku. Compose szuka .env w katalogu projektu, czyli w katalogu zawierającym plik Compose, i podstawia jego wartość w każdym znalezionym wystąpieniu ${NAME}.

Uruchom teraz usługę.

docker compose run --rm demo

printenv ALPINE_TAG kończy działanie ze statusem 1 i niczego nie wyświetla. Zmienna nie istnieje wewnątrz kontenera. Jest to najczęstsze nieporozumienie: .env konfiguruje plik Compose, a nie proces. Plik .env zawierający POSTGRES_PASSWORD=hunter2 nie ma żadnego wpływu na bazę danych, chyba że jakaś część pliku Compose się do niego odwołuje.

${NAME:-default} podaje wartość zastępczą, gdy zmienna jest nieustawiona lub pusta. ${NAME:?message} powoduje, że Compose odmówi uruchomienia i wyświetli podany komunikat. Jest to właściwy wybór dla wartości, która nie ma bezpiecznej wartości domyślnej.

env_file ładuje zmienne do kontenera

Atrybut env_file: określa co najmniej jeden plik, którego zawartość zostanie użyta jako zmienne środowiskowe 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

Polecenie wyświetla from_env_file. Format pliku zawiera zwykłe wiersze KEY=value, po jednym w każdym wierszu. Znak # na początku wiersza rozpoczyna komentarz. Nie jest to składnia powłoki. W większości przypadków cudzysłowy pozostają częścią wartości. Prefiksy export nie są wymagane. Nie należy umieszczać spacji wokół znaku =, ponieważ KEY = value tworzy zmienną o nazwie KEY , której wartość rozpoczyna się od spacji.

Brak ścieżki env_file powoduje błąd i zatrzymanie Compose. Jeśli plik może prawidłowo nie istnieć, należy oznaczyć go jako opcjonalny:

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

environment ustawia zmienne inline

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

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

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

Spowoduje to wyświetlenie from_my_shell. Jeśli zostanie uruchomione bez ustawienia GREETING w powłoce, Compose niczego nie ustawi i nie wyświetli ostrzeżenia. Należy znać przypadki niejawnego niepowodzenia przekazania zmiennej, ponieważ usługa uruchomiona z pustą zmienną hasła często uruchamia się poprawnie, ale pozostaje całkowicie niezabezpieczona.

Które ustawienie ma pierwszeństwo

Docker dokumentuje 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 z pliku środowiskowego, potem zwykłe environment w pliku Compose, następnie env_file, a na końcu dyrektywa ENV zapisana w obrazie.

W skrócie, na co dzień: environment: ma pierwszeństwo przed env_file:, a -e w wierszu poleceń ma pierwszeństwo przed oboma. 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 wyświetla from_environment, ponieważ environment: zastąpiło wartość w app.env. Drugie wyświetla from_cli. Żadne ustawienie w pliku Compose nie zastępuje wartości przekazanej w wierszu poleceń.

Gdy kontener działa tak, jakby konfiguracja nie została zastosowana, nie należy zgadywać. docker compose config wyświetla w pełni rozwiązaną konfigurację, a docker compose config --environment wyświetla zmienne interpolacji, z których korzysta Compose. Większość zgłoszeń typu „mój plik środowiskowy jest ignorowany” wynika z ustawienia tej samej wartości dwukrotnie, na dwóch różnych poziomach.

Dlaczego zmienne środowiskowe wyciekają

Ustawienie hasła w environment: powoduje zapisanie go w konfiguracji kontenera na dysku. Wartość ta jest widoczna dla każdego użytkownika należącego do grupy 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 inne ścieżki ujawniają tę samą wartość. docker compose config wyświetla ją w terminalu, przez co może zostać wklejona na forum pomocy technicznej. Każdy proces działający wewnątrz kontenera może odczytać /proc/1/environ, a każdy proces potomny dziedziczy tę zmienną. Procedury obsługi awarii aplikacji rutynowo zapisują całe środowisko w dzienniku lub raporcie błędu.

Przynależność do grupy docker daje w praktyce uprawnienia root na hoście. Nie należy więc traktować tej grupy jako granicy uprawnień. W przewodniku konta użytkowników z minimalnymi uprawnieniami na VPS wyjaśniono, dlaczego warto ograniczać członkostwo w tej grupie na każdym współdzielonym serwerze.

Sekrety Compose przechowują wartość w pliku

Compose obsługuje sekrety oparte na plikach. Wartość jest montowana w kontenerze jako plik, a nie wstrzykiwana do środowiska.

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 kontenerze pod ścieżką /run/secrets/db_password. Nazwa występująca po ukośniku jest nazwą sekretu z bloku najwyższego poziomu secrets:.

Sufiks _FILE jest konwencją używaną przez Docker Official Images, w tym postgres, mysql i mariadb. Ich skrypty entrypoint sprawdzają obecność VARNAME_FILE, odczytują plik i używają jego zawartości. Nie jest to funkcja platformy Docker, dlatego działa tylko wtedy, gdy obraz ją implementuje. Przed przyjęciem, że SOMETHING_FILE będzie obsługiwane, należy sprawdzić dokumentację obrazu. Aplikacje, które tego nie obsługują, często mogą samodzielnie odczytać plik podczas uruchamiania. Można też przekazać ścieżkę i zająć się tym we własnym entrypoint.

Należy zweryfikować działanie z poziomu 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 nic nie wyświetla, ponieważ wartość nigdy nie trafiła do środowiska. Na tym właśnie polega działanie: docker inspect w tym kontenerze pokazuje tylko nieszkodliwą ścieżkę.

Plik źródłowy na hoście należy odpowiednio zabezpieczyć, ponieważ sekret jest chroniony tylko tak dobrze, jak plik, za którym się znajduje:

chmod 600 db_password.txt

Praktyczny kompromis na VPS

Wiele samodzielnie hostowanych obrazów nie obsługuje zmiennych _FILE, dlatego jedynym sposobem ich przekazania są zmienne środowiskowe. W przypadku pojedynczego VPS zarządzanego przez jednego administratora realistycznym celem jest usunięcie wartości z pliku w katalogu projektu, który jest czytelny dla wszystkich, oraz nieumieszczanie ich w 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

install -m 600 tworzy plik od razu z ustawionymi uprawnieniami, więc nie występuje okres, w którym jest on czytelny dla wszystkich. Właścicielem pliku jest root, dlatego użytkownik niebędący root na tym serwerze nie może go odczytać. Każdy, kto może uruchomić docker, nadal może jednak odczytać wartość z kontenera. Należy dodać *.env i .env do .gitignore oraz zatwierdzić plik app.env.example zawierający nazwy kluczy z pustymi wartościami. Zatwierdzone hasło należy uznać za hasło wymagające rotacji.

Rotacja wartości oznacza ponowne uruchomienie usługi. Zmienne środowiskowe są odczytywane jednokrotnie podczas uruchamiania procesu kontenera, dlatego edycja pliku nie przynosi skutku do czasu 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 szyfrowania znajduje się poza plikiem compose.

Podział konfiguracji według środowiska

Compose domyślnie odczytuje .env z katalogu projektu. Za pomocą --env-file można wskazać inną lokalizację.

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

Wiele plików jest odczytywanych w kolejności podanej na liście, a późniejsze pliki zastępują wcześniejsze. Niesekretne wartości domyślne należy przechowywać w pliku objętym kontrolą wersji, a dane uwierzytelniające w pliku, który nigdy nie opuszcza serwera. To samo dotyczy env_file: — w przypadku zduplikowanego klucza obowiązuje wartość z ostatniego wymienionego pliku.

FAQ

Dlaczego plik .env jest ignorowany wewnątrz kontenera?

Nie jest ignorowany. Plik .env podstawia wyłącznie symbole zastępcze ${NAME} w pliku Compose. Nie ustawia zmiennych wewnątrz kontenera. Aby przekazać wartość do kontenera, należy użyć odwołania: environment: { KEY: "${NAME}" } albo zamiast tego użyć env_file: ./that-file.env.

Czy environment ma pierwszeństwo przed env_file, czy odwrotnie?

Pierwszeństwo ma environment:. Zgodnie z dokumentacją Docker kolejność umieszcza atrybut environment wyżej niż atrybut env_file, a oba znajdują się poniżej docker compose run -e w wierszu poleceń. Jeśli klucz zostanie ustawiony w obu miejscach, wartość z env_file zostanie po cichu pominięta.

Jak wyświetlić końcową wartość używaną przez Compose?

Należy uruchomić docker compose config, aby wyświetlić w pełni rozwiązany plik Compose po zastosowaniu całej interpolacji. W przypadku już uruchomionego kontenera docker inspect <container> --format '{{json .Config.Env}}' pokazuje dokładnie, jakie wartości otrzymał jego proces.

Czy sekrety 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 na dysku hosta pozostaje niezaszyfrowany. Korzyść dotyczy zakresu dostępu, a nie szyfrowania: wartość nie trafia do środowiska kontenera, do danych wyjściowych docker inspect ani do zrzutów po awarii, które zawierają środowisko.

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łą pozostałą część wiersza jako wartość, dlatego cudzysłowy zwykle stają się znakami występującymi dosłownie w wartości. Nie należy umieszczać spacji wokół =, ponieważ klucz będzie wtedy zawierał końcową spację i żadne odwołanie nie zostanie dopasowane.