Docker Compose использует переменные в двух принципиально разных местах. Понимание разницы уберёт большую часть путаницы.
- Подстановка в
docker-compose.yml(интерполяция${VAR}) — когда Compose читает файл и заменяет${VAR}в полях, напримерports,healthcheck,command,imageи т.д. - Передача переменных внутрь контейнера — когда пары
KEY=VALUEреально попадают в окружение процесса внутри контейнера (вы видите их вenv).
Важное: эти две задачи используют похожие имена переменных, но разные источники и разные правила.
Когда Compose встречает ${VAR} — порядок поиска такой:
- Переменные shell, откуда запущен
docker compose(например,export VAR=...). - Файл
.env, рядом сdocker-compose.yml, если в shell значение не найдено. - Если указан дефолт:
${VAR:-default}— используетсяdefault. - Если
${VAR:?msg}и переменной нет — Compose выдаст ошибку и остановится (сmsg).
Ключевой вывод: env_file: не участвует в поиске значений для ${VAR}.
Источники значений внутри контейнера (в порядке приоритета):
environment:в сервисе (docker-compose.yml) — явное задание;env_file:в сервисе — загрузка файла(ов)KEY=VALUE;ENVвDockerfileобраза.
Если одно и то же имя задано в environment: и env_file:, то используется значение из environment: (override).
Что делает:
env_file: - path/to/fileчитает файл(ы) форматаKEY=VALUEи передаёт их внутрь контейнера для данного сервиса.
Ключевые моменты:
env_fileдействует только внутри того сервиса, где он прописан. Другие сервисы такие пары не увидят, если у них не указан тот жеenv_file.- Путь может быть относительным (от папки с
docker-compose.yml) или абсолютным. - Содержимое читается буквально: подстановки
${...}не выполняются внутриenv_file.
Пример postgres.env:
POSTGRES_DB=postgres
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgresПример docker-compose.yml (фрагмент):
services:
postgres:
image: postgres:17-alpine
env_file:
- ./postgres.envenvironment: — это явный и контролируемый способ задать переменные, которые попадут в контейнер.
Плюсы environment::
- Чётко видно, какие переменные важны — удобно как документация;
- Можно использовать подстановку
${VAR}— тогда Compose возьмёт значение из shell или из.envпри парсинге; - Значения из
environment:перекрывают значения изenv_file:.
Можно ли обойтись без environment:?
- Да: если ты используешь
env_file:и он содержит все необходимые пары, либо если переменные выставлены в shell и ты хочешь унаследовать их (через- VARв environment или иным способом). - Но
environment:полезен для переопределений и для явной документации.
Пример (обе опции вместе):
services:
postgres:
env_file:
- .env
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # явное дублирование/документация
TZ: Europe/Moscow-
Зачем писать
env_file: path?- Чтобы подключить файл с переменными (не называющийся
.envили лежащий в другом месте) и передать пары в контейнер данного сервиса.
- Чтобы подключить файл с переменными (не называющийся
-
env_fileдействует только для текущего сервиса?- Да. Он применяется только к тому сервису, где указан.
-
Можно ли без
environment:?- Можно, если
env_file:покрывает всё. Ноenvironment:даёт контроль и возможность переопределять.
- Можно, если
-
Можно ли использовать переменные из
env_fileодного сервиса как${VAR}вdocker-compose.yml?- Нет. Подстановка
${VAR}(при чтенииdocker-compose.yml) смотрит только в shell и в.envрядом с compose.
- Нет. Подстановка
.env:
POSTGRES_DB=postgres
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgresdocker-compose.yml (фрагмент):
services:
postgres:
image: postgres:17-alpine
env_file:
- .env # передаёт пары в контейнер
environment:
POSTGRES_DB: ${POSTGRES_DB} # документирует/гарантирует
ports:
- "${POSTGRES_PORT}:5432" # подстановка из .envЗдесь .env обеспечивает и подстановку (ports), и передачу в контейнер (через env_file или environment).
services:
app:
env_file:
- shared.env
worker:
env_file:
- shared.envОба сервиса получат одинаковые пары внутри контейнеров.
common.env содержит DEBUG=true, но для web нужно DEBUG=false:
services:
web:
env_file:
- common.env
environment:
DEBUG: "false" # перекроет DEBUG из common.env.env— источник для${VAR}при парсингеdocker-compose.yml(shell >.env> дефолт). Не попадает в контейнер автоматически.env_file:— файлKEY=VALUE, который передаётся в контейнер сервиса (scope: только сервис).environment:— явные переменные вdocker-compose.yml, попадают в контейнер и перекрываютenv_file:.- Приоритет (внутри контейнера):
environment→env_file→Dockerfile ENV.
- Для локальной разработки: храните общие значения в
.envи подключайте его вenv_fileили черезenvironment. - Для больших проектов: разделяйте на
common.env,postgres.env,app.envи подключайте только нужные файлы к соответствующим сервисам. - Никогда не коммить секреты в публичный репозиторий. Для секретов используйте секрет-менеджер, Docker secrets или переменные CI.
Что будет, если env_file имеет имя отличное от .env (например .test.env) и мы пытаемся использовать переменные из него для подстановки в docker-compose.yml?
Короткий ответ: переменные из env_file не используются для подстановки ${VAR} при парсинге docker-compose.yml. Если значение есть только в .test.env, а в shell и в проектном .env его нет — подстановка вернёт пустую строку, и это может привести к ошибкам (например, некорректной спецификации ports).
Детали и примеры поведения:
- Допустим, у тебя есть файл
.test.env:
POSTGRES_PORT=5433и docker-compose.yml с использованием подстановки:
services:
postgres:
image: postgres:17-alpine
ports:
- "${POSTGRES_PORT}:5432"
env_file:
- .test.env-
При разборе
docker-compose.ymlCompose ищет${POSTGRES_PORT}в shell, затем в файле.envрядом с compose.env_file: .test.envпри этом не используется для подстановки. Если в shell и в.envпеременной нет,${POSTGRES_PORT}станет пустой строкой. -
В результате строка
"${POSTGRES_PORT}:5432"превратится в":5432"— это может привести к ошибке при обработке конфигурации (например,invalid port specification) или к неожиданному поведению.
Как избежать проблемы — варианты решения:
-
Переименовать
.test.envв.env(рядом сdocker-compose.yml) — тогда переменные будут видны для подстановки и останутся доступны в контейнере при подключении черезenv_fileили при явном указании вenvironment:. -
Экспортировать переменные в shell перед запуском:
export POSTGRES_PORT=5433
docker compose up -d- Использовать флаг CLI
--env-file(Compose V2+) — указать файл, который Compose должен использовать для интерполяции при запуске:
docker compose --env-file .test.env up -d-
Не полагаться на подстановку в
ports— передавать значение явно черезenvironment:(но учти: если ты пишешьPOSTGRES_PORT: ${POSTGRES_PORT}вenvironment:, то снова подстановка ищет shell/.env, а неenv_file). То есть этот вариант сам по себе не решит проблему без того, чтобы переменная была доступна для подстановки. -
Сделать проверку "fail fast" в Compose с помощью
${VAR:?message}— если переменная не задана в shell/.env, Compose сразу выдаст понятную ошибку:
ports:
- "${POSTGRES_PORT:?POSTGRES_PORT is required}:5432"Это удобнее, чем получать непонятную ошибку invalid port specification.
Резюме:
env_file:передаёт пары внутрь контейнера, но не участвует в интерполяции${VAR}при чтенииdocker-compose.yml.- Если файл имеет имя отличное от
.env(например.test.env) и переменные есть только в нём, то для${VAR}они недоступны, если ты не используешь--env-fileили не экспортировал их в shell или не переименовал файл в.env.