Skip to main content

Як використовувати змінні середовища в Docker?

Environment-змінні це як Docker передає runtime-конфігурацію застосунку всередині container. Чотири місця, де їх ставити, кожне з різним scope і persistence. Знання різниць тримає secret поза image-history.

Теорія

TL;DR

  • ENV у Dockerfile = запечено у image. Видно у image-history назавжди. Для non-secret дефолтів.
  • -e KEY=value на docker run = per-container, не в image.
  • --env-file file.env = bulk env з файлу. Той самий scope, що й -e.
  • Compose environment: і env_file: = runtime, project-scoped.
  • ARG у Dockerfile ≠ env var. Build-time only, зникає у runtime.
  • Ніколи не використовуй env для secret у проді. Вони з'являються у docker inspect, ps aux, image-history і багатьох логах.

ENV у Dockerfile

dockerfile
FROM node:22-alpine ENV NODE_ENV=production ENV PORT=3000 # АБО multi-line ENV NODE_ENV=production \ PORT=3000 \ LOG_LEVEL=info

Значення це частина image. Кожен, хто запускає image, отримує їх за замовчуванням. Override під час запуску:

bash
docker run -e PORT=8080 myapp # PORT тепер 8080, NODE_ENV ще production

Важливо: ENV-значення з'являються у docker history і docker inspect. Ніколи не запікай secret тут.

-e і --env-file на docker run

bash
# Одна змінна docker run -e DATABASE_URL=postgres://... myapp # Кілька docker run -e DB_HOST=db -e DB_PORT=5432 -e DB_USER=postgres myapp # Прокинути з shell (без value = читай host env) export API_KEY=secret123 docker run -e API_KEY myapp # Bulk з файлу docker run --env-file .env myapp

Формат .env-файлу:

# Рядки з # це коментарі DB_HOST=db DB_PORT=5432 NODE_ENV=production

Без лапок, без shell-розгортання, один KEY=VALUE на рядок.

Compose: environment: і env_file:

yaml
services: api: image: myapp environment: NODE_ENV: production # map-форма DATABASE_URL: postgres://... DEBUG: "" # порожнє значення (різне від відсутнього) env_file: - .env # file-форма - .env.local # пізніший override раніший db: image: postgres:16 environment: - POSTGRES_PASSWORD=${DB_PASSWORD} # list-форма, з shell-style інтерполяцією - POSTGRES_DB=${DB_NAME:-app} # default, якщо DB_NAME unset

Compose-інтерполяція: ${VAR} і ${VAR:-default} посилаються на змінні з твого shell або з файлу .env поряд з compose.yaml. Цей .env для самого Compose, використовується під час YAML-парсингу. Інакше, ніж env_file:, що передається у container.

ARG vs ENV

dockerfile
ARG NODE_VERSION=22 FROM node:${NODE_VERSION}-alpine ARG BUILD_VERSION # передано через --build-arg, доступно ПІД ЧАС білду тільки RUN echo "Building $BUILD_VERSION" > /app/version.txt ENV APP_VERSION=$BUILD_VERSION # щоб було доступно у runtime, копіюй ARG у ENV
bash
docker build --build-arg BUILD_VERSION=1.2.3 -t myapp .
  • ARG = build-time only. Зникає після завершення docker build.
  • ENV = runtime. Живе у image і running container.
  • Поширений патерн: отримай значення як ARG (щоб білд міг використати), скопіюй у ENV (щоб runtime міг бачити).

Чому secret НЕ належить у env

bash
docker run -e DB_PASSWORD=hunter2 myapp

Витікає:

  • docker inspect <container> показує env у plain text
  • ps auxe на host може показати env PID 1 container
  • Власний /proc/1/environ container читається зсередини
  • Багато app-фреймворків логують повний env при старті (особливо у dev)
  • Дочірні процеси успадковують env

Краще роби так:

  • BuildKit secret-mount для build-time secret:
    dockerfile
    RUN --mount=type=secret,id=npmrc cp /run/secrets/npmrc ~/.npmrc && npm ci
  • Docker Swarm secret або K8s secret, змонтовані як файли у runtime
  • External secret-manager (AWS Secrets Manager, HashiCorp Vault), читаються при старті
  • Для локального dev: .env.local поза git нормально

Типові помилки

Класти паролі у ENV Dockerfile

dockerfile
# НЕПРАВИЛЬНО: назавжди у image-history ENV DB_PASSWORD=hunter2

Пароль у image-manifest, видно усім, хто pull'ить image. Ніколи не запікай secret.

Лапки у .env-файлах

# НЕПРАВИЛЬНО: літеральні лапки стають частиною значення VAR="value with spaces" # значення буквально `"value with spaces"` # ПРАВИЛЬНО (Docker трактує значення як plain strings) VAR=value with spaces

Docker .env це не shell. Лапки не потрібні. Якщо ставиш лапки, вони стають частиною значення.

Забути, що .env для Compose-інтерполяції, env_file: для container

yaml
services: api: environment: DB_PASSWORD: ${DB_PASSWORD} # ← читається з host-shell або .env при YAML-парсингу env_file: .env.app # ← передається container як env-змінні

Два різні файли, два різні scope. .env (дефолтний lookup Compose) для YAML; env_file: для container.

Спроба інтерполювати у runtime у ENV

dockerfile
ENV PATH=$PATH:/app/bin # Працює: $PATH тут це попереднє ENV-значення ENV DEBUG=$LOG_LEVEL # Може НЕ працювати: залежить від ARG/ENV scoping під час білду

Dockerfile-інтерполяція у момент ENV використовує build-time контекст, не runtime. Для runtime-композиції бери shell-wrapper або entrypoint-скрипт.

Реальне застосування

  • Twelve-factor застосунки: уся config через env, усі зміни поведінки через env, без config-файлів у image. Compose / K8s передають env при деплої.
  • CI/CD: secret інжектяться як env через CI secret-store (GITHUB_TOKEN, DOCKERHUB_TOKEN).
  • Локальний dev: .env.local (gitignored) тримає dev-override; compose.yaml посилається через ${VAR}.
  • Прод: secret через mounted-файли (Swarm secret, K8s secret, Vault sidecar), не сирий env. Non-secret config все одно через env.

Питання для поглиблення

Q: Який пріоритет, коли та сама змінна задана у кількох місцях?


A: Для Compose: env_file < environment у service < host shell-env. Пізніше виграє. Для docker run: флаг -e виграє над Dockerfile ENV.

Q: Як побачити effective env container?


A: docker exec <name> env. Показує runtime-env, включно з ENV з image і -e під час запуску.

Q: Чи можу зняти успадковану env-змінну?


A: Постав її порожнім: -e DEBUG=. Нота: порожнє не те саме, що unset; process.env.DEBUG буде "", не undefined. Щоб реально unset, збирай новий image без ENV DEBUG=..., Dockerfile ENV можна прибрати лише через відсутність у Dockerfile, не unset командою.

Q: Яка різниця між --env-file і Compose env_file:?


A: Та сама ідея. --env-file для docker run. Compose env_file: той самий механізм всередині YAML.

Q: (Senior) Як безпечно інжектити secret у Compose-прод-деплой?


A: Бери Docker Swarm secret (file-mounted, ніколи в env), або external secret-manager, що застосунок читає при старті, або sealed-secrets-підходи з init-container, що fetch і пишуть у tmpfs, який застосунок читає. Уникай plain env_file: з secret у plaintext. Лінія, яку не перетинаєш: secret у env або у image-шарах.

Приклади

Реалістичний Compose з правильно шарованим env

yaml
# compose.yaml services: api: image: myapp:${TAG:-latest} environment: NODE_ENV: production LOG_LEVEL: info DATABASE_URL: postgres://api:${DB_PASSWORD}@db:5432/app env_file: - .env.app # додаткова non-secret config db: image: postgres:16 environment: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: app
bash
# .env (Compose-інтерполяція) TAG=v1.2.3 DB_PASSWORD=hunter2 # .env.app (передається api-container як env) FEATURE_X=enabled FEATURE_Y=disabled
bash
$ docker compose up -d # api отримає: # NODE_ENV=production (з `environment:`) # LOG_LEVEL=info (з `environment:`) # DATABASE_URL=postgres://api:hunter2@db:5432/app (інтерпольовано) # FEATURE_X=enabled, FEATURE_Y=disabled (з .env.app через env_file:)

Build-time arg + runtime env

dockerfile
ARG BUILD_VERSION FROM alpine:3.21 ARG BUILD_VERSION ENV APP_VERSION=$BUILD_VERSION RUN echo $APP_VERSION > /version.txt CMD ["sh", "-c", "echo Running version $APP_VERSION; cat /version.txt; sleep 100"]
bash
$ docker build --build-arg BUILD_VERSION=1.2.3 -t demo . $ docker run --rm demo Running version 1.2.3 1.2.3

ARG передає значення у білд; копіювання у ENV робить його доступним у runtime.

BuildKit secret для npmrc

dockerfile
# syntax=docker/dockerfile:1.7 FROM node:22-alpine WORKDIR /app COPY package*.json ./ RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \ npm ci COPY . .
bash
$ docker buildx build --secret id=npmrc,src=$HOME/.npmrc -t myapp .

.npmrc доступний для RUN-кроку, але ніколи не приземляється у жоден image-шар. Жодного ENV NPM_TOKEN=... ніде.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів