SSD Nodes Learn 8GB RAM — $66/рік
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-01

Docker Compose: різниця між .env, env_file і secrets

Пояснюємо різницю між .env, env_file та environment у Docker Compose: що має пріоритет, як перевірити підстановку і чому паролі слід зберігати в secrets.

Три механізми, які називають env-файлом

Docker Compose має три окремі механізми зі схожими назвами, що часто спричиняє плутанину. Файл .env підставляє значення замість заповнювачів ${VARIABLE} безпосередньо у compose.yaml ще до того, як Compose розбере цей файл. Атрибут env_file: завантажує файл із парами ключ/значення до середовища контейнера. Атрибут environment: безпосередньо задає змінні контейнера, записані у compose-файлі. Вони не є взаємозамінними. Якщо два з них задають те саме ключове значення, результат визначається задокументованим порядком пріоритетів.

У цьому посібнику показано роботу кожного механізму та за допомогою команди, яку можна виконати, перевірено порядок пріоритетів. Далі розглянуто важливіший аспект: змінні середовища може прочитати будь-хто, хто може виконати docker inspect, тому паролі не слід зберігати в них. Якщо ви ще не знайомі з compose-файлами, спочатку перегляньте Основи Docker Compose на VPS, а потім поверніться сюди для налаштування.

Файл .env призначений для compose-файлу, а не для контейнера

Створіть каталог і помістіть у нього два файли.

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

Тепер перевірте, що саме розібрав Compose.

docker compose config

У виводі показано image: alpine:3.20. Заповнювач зник, оскільки інтерполяція відбулася під час розбору. Compose шукає .env у каталозі проєкту, тобто в каталозі, що містить compose-файл, і підставляє його замість кожного ${NAME}.

Тепер запустіть сервіс.

docker compose run --rm demo

printenv ALPINE_TAG завершує роботу зі статусом 1 і нічого не виводить. Цієї змінної немає всередині контейнера. Це найпоширеніше непорозуміння: .env налаштував compose-файл, а не процес. Файл .env із POSTGRES_PASSWORD=hunter2 не впливає на вашу базу даних, якщо якась частина compose-файлу явно не посилається на нього.

${NAME:-default} задає резервне значення, якщо змінну не встановлено або вона порожня. ${NAME:?message} змушує Compose відмовитися від запуску та вивести ваше повідомлення. Це правильний вибір для значення, яке не має безпечного значення за замовчуванням.

env_file: завантажує змінні в контейнер

Атрибут env_file: задає один або кілька файлів, вміст яких стає змінними середовища контейнера.

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

Ця команда виводить from_env_file. Формат файлу — це звичайні рядки KEY=value, по одному в кожному рядку; коментар починається з #. Це не shell-синтаксис. У більшості випадків лапки зберігаються як частина значення, а префікси export не потрібні. Не ставте пробіли навколо знака =, оскільки KEY = value створює змінну з назвою KEY , значення якої починається з пробілу.

Відсутній шлях env_file спричиняє помилку, і Compose припиняє роботу. Позначте файл як необов’язковий, якщо він може бути відсутнім:

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

змінні середовища задаються вбудовано

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

Приймаються два синтаксиси: наведена вище форма зіставлення та форма списку з використанням - GREETING=from_environment. Вони працюють однаково. Форма списку має додаткову особливість: ключ без значення передає змінну з оболонки, у якій ви запустили docker compose.

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

Це виводить from_my_shell. Якщо запустити цю команду, не задавши GREETING в оболонці, Compose нічого не встановить і не покаже попередження. Про непомітні помилки передавання змінних варто знати, оскільки служба, яка запускається з порожньою змінною пароля, часто стартує успішно, але залишається повністю відкритою.

Що має вищий пріоритет

Docker описує порядок пріоритетів — від найвищого: спочатку docker compose run -e у командному рядку, потім environment або env_file, значення яких підставляється з оболонки або env-файлу, далі звичайний environment у compose-файлі, потім env_file, а потім директива ENV, вбудована в образ.

Коротко для щоденної роботи: environment: має вищий пріоритет за env_file:, а -e у командному рядку має вищий пріоритет за обома. Перевірте це в одному файлі.

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

Перша команда виводить from_environment, тому environment: замінило значення у app.env. Друга команда виводить from_cli. Ніщо у compose-файлі не замінює значення з командного рядка.

Якщо контейнер працює так, ніби вашу конфігурацію не застосовано, не вгадуйте причину. docker compose config виводить повністю оброблений файл, а docker compose config --environment — змінні інтерполяції, з якими працює Compose. Більшість повідомлень про те, що "мій env-файл ігнорується", виникає через те, що одне значення задано двічі на різних рівнях.

Чому витікають змінні середовища

Установіть пароль у environment:, і його буде збережено в конфігурації контейнера на диску. Значення буде видиме будь-якому користувачу з групи docker.

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

Вивід містить "DB_PASSWORD=hunter2" у відкритому вигляді. Ще три шляхи розкривають те саме значення. docker compose config виводить його в термінал, після чого його можна вставити у форум технічної підтримки. Будь-який процес усередині контейнера може прочитати /proc/1/environ, а кожен дочірній процес успадковує цю змінну. Крім того, обробники аварійного завершення застосунків регулярно записують усе середовище в журнал або звіт про помилку.

Членство в групі docker фактично надає права root на хості. Тому на цю групу не можна покладатися як на межу привілеїв. У посібнику облікові записи користувачів із мінімальними привілеями на VPS пояснюється, чому доступ до цієї групи варто обмежити на будь-якому спільному сервері.

Секрети Compose зберігають значення у файлі

Compose підтримує секрети на основі файлів. Значення монтується в контейнер як файл, а не передається в середовище.

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

Секрет монтується в контейнері за шляхом /run/secrets/db_password. Ім’я після косої риски — це ім’я секрету з блоку верхнього рівня secrets:.

Суфікс _FILE — це угода, яку використовують Docker Official Images, зокрема postgres, mysql і mariadb. Їхні скрипти entrypoint перевіряють наявність VARNAME_FILE, читають файл і використовують його вміст. Це не функція Docker, тому вона працює лише в образах, де її реалізовано. Перевірте документацію образу, перш ніж припускати, що SOMETHING_FILE буде враховано. Програми, які не підтримують цю угоду, часто можуть самостійно прочитати файл під час запуску. Також можна передати шлях і доручити це власному entrypoint.

Перевірте це зсередини запущеного контейнера:

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

Перша команда виводить пароль. Друга нічого не виводить, оскільки значення не потрапило в середовище. У цьому й полягає суть: docker inspect у цьому контейнері показує лише нешкідливий шлях.

Захистіть вихідний файл на хості, оскільки секрет має такий самий рівень конфіденційності, як і файл, у якому він зберігається:

chmod 600 db_password.txt

Практичний компроміс для VPS

Багато готових образів для самостійного розгортання не підтримують змінні _FILE, тому змінні середовища є єдиним варіантом. На VPS з одним адміністратором реалістична мета — не зберігати значення у файлі, доступному для читання всіма користувачами, у каталозі проєкту та не додавати їх до 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 створює файл із потрібними правами доступу, тому не виникає проміжку часу, коли його можуть читати всі користувачі. Власником файлу є root, тому звичайний користувач на сервері не може його прочитати. Проте будь-хто, хто може виконати docker, усе одно може прочитати значення з контейнера. Додайте *.env і .env до .gitignore та зафіксуйте у репозиторії app.env.example, який містить назви ключів із порожніми значеннями. Опублікований пароль потрібно змінити.

Зміна значення означає перезапуск служби. Змінні середовища зчитуються один раз під час запуску процесу контейнера, тому редагування файлу нічого не змінить, доки ви не виконаєте docker compose up -d --force-recreate db. Такий самий підхід використано в посібнику n8n за HTTPS на VPS, де ключ шифрування зберігається поза compose-файлом.

Розділення конфігурації для різних середовищ

Compose за замовчуванням читає .env з каталогу проєкту. Укажіть інше розташування за допомогою --env-file.

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

Файли читаються послідовно, а параметри з наступних файлів замінюють параметри з попередніх. Зберігайте несекретні значення за замовчуванням у файлі, який додається до репозиторію, а секрети — у файлі, який ніколи не залишає сервер. Те саме стосується env_file:: якщо ключ дублюється, використовується значення з останнього вказаного файлу.

FAQ

Чому мій файл .env ігнорується всередині контейнера?

Його не ігнорують. Файл .env лише підставляє заповнювачі ${NAME} у compose-файлі. Він не встановлює змінні всередині контейнера. Щоб передати значення в контейнер, явно вкажіть його: environment: { KEY: "${NAME}" } або використайте env_file: ./that-file.env.

Що має вищий пріоритет: environment чи env_file?

Вищий пріоритет має environment:. Задокументований порядок Docker ставить атрибут environment вище за атрибут env_file, а обидва мають нижчий пріоритет, ніж docker compose run -e у командному рядку. Якщо ключ задано в обох місцях, значення з env_file мовчки не використовується.

Як переглянути остаточне значення, яке використає Compose?

Виконайте docker compose config, щоб вивести повністю оброблений compose-файл із застосованою інтерполяцією. Для контейнера, який уже запущено, docker inspect <container> --format '{{json .Config.Env}}' покаже точно те, що отримав його процес.

Чи шифруються секрети Compose?

Ні. Секрет на основі файлу монтується в контейнер як звичайний файл за шляхом /run/secrets/<name>, а вихідний файл зберігається на диску хоста без шифрування. Перевага полягає в обмеженні області доступу, а не в шифруванні: значення не потрапляє в середовище контейнера, у вивід docker inspect або у дампи після аварійного завершення, які містять середовище.

Чи можна використовувати лапки та пробіли у файлі env?

Використовуйте KEY=value with spaces і не ставте лапки. Compose сприймає весь залишок рядка як значення, тому лапки зазвичай стають його буквальними символами. Ніколи не ставте пробіли навколо =, оскільки тоді ключ міститиме кінцевий пробіл і жодне значення не відповідатиме йому.