SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-10-23

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

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

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

Docker Compose має три окремі механізми зі схожими назвами, що часто спричиняє плутанину. Файл .env підставляє значення замість заповнювачів ${VARIABLE} безпосередньо у compose.yaml після того, як Compose прочитає YAML, але до перевірки або запуску будь-яких компонентів. Атрибут 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. Compose шукає .env у каталозі проєкту: у каталозі, заданому за допомогою --project-directory, якщо його вказано; інакше — у каталозі першого файлу, переданого через -f; якщо жоден із цих варіантів не застосовується — у поточному каталозі. Він підставляє значення замість заповнювачів ${NAME}, які знаходить у значеннях YAML. Ключі не інтерполюються, а подвоєний $$ створює літерал долара замість підстановки.

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

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, хоча Compose обробляє файл за кількома правилами, подібними до shell. Зовнішні лапки видаляються, тому KEY="value" і KEY=value дають однаковий результат. У значеннях у подвійних лапках і без лапок виконується інтерполяція заповнювачів ${...}, а значення в одинарних лапках використовуються буквально. Префікс export дозволений, але не потрібен. Пробіли навколо знака = видаляються. Зверніть увагу на вбудований коментар: у значенні без лапок пробіл перед # починає коментар, тому значення, що містить #, потрібно взяти в лапки.

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

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

environment передає змінні в рядку

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

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

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

Це виводить from_my_shell. Якщо запустити команду без заданої в shell змінної GREETING, Compose нічого не встановлює та не показує попередження. Про непомітні помилки pass-through варто знати, оскільки сервіс із порожньою змінною пароля часто успішно запускається й фактично залишається повністю відкритим.

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

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 виводить його в термінал, через що воно може потрапити в допис на форумі підтримки. Дочірні процеси за замовчуванням успадковують змінні, якщо батьківський процес навмисно не запускає їх із заміненим середовищем. Процес, який працює від імені того самого користувача, що й PID 1, або має доступ до CAP_SYS_PTRACE, може прочитати її з /proc/1/environ. У контейнері, де все працює від імені root, це стосується кожного процесу. Якщо застосунок працює від імені непривілейованого користувача, а PID 1 — від імені root, він натомість отримує помилку доступу до цього файлу, хоча змінна все одно залишається в його власному середовищі. Крім того, обробники аварійного завершення застосунків регулярно записують усе середовище до журналу або звіту про помилку.

Членство в групі 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-файлі. Він також може задавати власні змінні Compose, наприклад COMPOSE_PROJECT_NAME або COMPOSE_FILE. Але він ніколи не задає змінні всередині контейнера. Щоб передати значення в контейнер, явно вкажіть його: 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?

Так. Compose видаляє зовнішні лапки, тому KEY="value with spaces" і KEY=value with spaces обидва задають value with spaces. Використовуйте одинарні лапки, якщо значення містить $, який не потрібно інтерполювати. Беріть у лапки будь-яке значення, що містить #, оскільки без лапок із нього починається коментар. Пробіли навколо = видаляються, тому KEY = value усе одно задає KEY.

Чи була ця стаття корисною?