ARG и ENV в Dockerfile: отличия, --build-arg и безопасная параметризация сборки | AdminWiki

ARG и ENV в Dockerfile: отличия, --build-arg и безопасная параметризация сборки

26 сентября 2026 10 мин. чтения

ARG задаёт значение только на время сборки, ENV остаётся в образе и попадает в запущенный контейнер. Практическое правило: версии зависимостей, имена базовых образов и флаги сборки передавайте через ARG и --build-arg, а настройки, которые нужны процессу в рантайме, храните в ENV или передавайте при запуске контейнера.

Ошибка в выборе инструкции даёт два типовых сбоя: образ на стенде ведёт себя иначе, чем ожидалось, либо пароль из флага сборки остаётся в истории образа и доступен любому, кто этот образ скачал. Разбираем область видимости, влияние на кэш, готовые команды и чек-лист.

ARG и ENV в Dockerfile: ключевые отличия

Сравнение по признакам, которые чаще всего нужны при отладке сборки:

КритерийARGENV
Когда доступенНа этапе сборки, в инструкциях после объявленияНа этапе сборки и в запущенном контейнере
Область видимостиОдна стадия сборки; до первого FROM работает глобальноВсе последующие инструкции стадии и дочерние образы
Значение по умолчаниюARG VERSION=1.0ENV APP_ENV=prod
Переопределениеdocker build --build-arg VERSION=2.0docker run -e APP_ENV=staging
След в образеВ конфигурацию образа не попадает, но виден в истории сборки, если использован в RUNХранится в конфигурации образа, виден через docker inspect
Влияние на кэшСбрасывает кэш, если изменилось значение, использованное в RUN или COPYСбрасывает кэш начиная со строки ENV

Короткая формула: ARG параметризует сборку, ENV описывает среду выполнения. Один параметр может пройти через оба: ARG VERSION=1.0 принимает значение из флага, а ENV APP_VERSION=$VERSION закрепляет его в конфигурации образа.

Область видимости и время жизни

ARG живёт с момента объявления до конца текущей стадии. Объявить его после FROM и ждать, что он будет виден в другой стадии, не получится: подставится пустая строка, и сборка либо упадёт, либо соберёт не то, что вы ожидали.

Отдельный случай - ARG перед первым FROM. Такой аргумент работает в глобальной области и подставляется только в строку FROM, поэтому имя базового образа можно менять без правки Dockerfile.

ARG NODE_TAG=20-alpine
FROM node:${NODE_TAG} AS build
ARG BUILD_ENV=production
ENV APP_ENV=$BUILD_ENV
RUN echo "build for $BUILD_ENV"

FROM nginx:1.27-alpine AS runtime
ARG BUILD_ENV=production
COPY --from=build /app/dist /usr/share/nginx/html

ENV наследуется дочерними образами: если app:1.0 служит базой для app:2.0, значение APP_ENV останется доступным. ARG так не передаётся, его объявляют в каждой стадии заново. Переменные ARG не сохраняются в собранном образе как переменные ENV (Dockerfile reference).

Влияние на слои и кэш

Инструкция ARG создаёт слой нулевого размера: она не добавляет и не меняет файлы, а лишь фиксирует состояние build-аргумента на этом шаге, поэтому объёма образу не прибавляет (Docker Build Args: What Are They and How to Use Them). При этом значения ARG влияют на кэш сборки: если build-аргумент меняется, промах кэша происходит на первой инструкции, которая его использует (How to Use the ARG Instruction for Build-Time Variables). После инвалидации кэша все последующие команды Dockerfile генерируют новые образы и кэш не используется (Build cache invalidation). ENV меняет конфигурацию образа, поэтому правка значения тоже сбрасывает кэш с этой строки.

Отсюда рабочая привычка: объявляйте ARG прямо перед инструкцией, где он нужен, а не блоком в начале Dockerfile. Тогда смена версии приложения не будет каждый раз пересобирать установку зависимостей.

COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY . .
ARG CACHE_BUST=1
RUN echo "cache bust $CACHE_BUST"

Сбросить кэш только для одного шага можно управляемым значением: docker build --build-arg CACHE_BUST=$(date +%s) -t app .

Механику слоёв и порядок инструкций подробно разбираем в статье сборка образа пошагово: Dockerfile, кэширование и multi-stage build.

Как передавать значения через --build-arg: примеры и синтаксис

Значения приходят из флага --build-arg. Флагов может быть сколько угодно, каждый задаёт один параметр:

docker build --build-arg BUILD_ENV=staging --build-arg VERSION=1.4.2 -t app:1.4.2 .

Со стороны Dockerfile нужны объявления:

FROM node:20-alpine
ARG BUILD_ENV=production
ARG VERSION=dev
RUN echo "version $VERSION, env $BUILD_ENV"
  • Значение по умолчанию подставляется, когда флаг не передан. Для необязательных параметров его задавайте всегда.
  • Флаг без знака равенства берёт значение из переменной окружения вашей оболочки: docker build --build-arg VERSION .
  • Для ENV флаг не работает. ENV получает значение из Dockerfile или перекрывается при запуске через docker run -e. Значение ENV можно взять из ARG: ENV APP_ENV=$BUILD_ENV.

Проверить результат сборки можно тремя командами:

docker build --build-arg VERSION=2.0 -t app:test .
docker history --no-trunc app:test
docker run --rm app:test env | grep APP_ENV
docker inspect --format '{{json .Config.Env}}' app:test

docker history --no-trunc показывает команды сборки, включая подставленные значения ARG. Результат ENV смотрите в блоке Config.Env у docker inspect либо внутри контейнера. Сам ARG в конфигурации образа не остаётся, поэтому проверить его значение после сборки по метаданным нельзя, смотрите историю.

Использование ARG в RUN и COPY для сборки под разные окружения

Один Dockerfile закрывает dev, staging и production, если ветвление внутри RUN опирается на ARG:

FROM node:20-alpine AS build
ARG BUILD_ENV=production
ARG CONFIG_FILE=config.production.json
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
COPY ${CONFIG_FILE} /app/config.json
RUN if [ "$BUILD_ENV" = "production" ]; then npm run build:prod; else npm run build:dev; fi

Сборка запускается с нужным набором флагов:

docker build --build-arg BUILD_ENV=staging --build-arg CONFIG_FILE=config.staging.json -t app:staging .
ОкружениеФлаги сборкиРезультат в образе
dev--build-arg BUILD_ENV=development --build-arg CONFIG_FILE=config.dev.jsondev-сборка приложения и dev-конфиг
staging--build-arg BUILD_ENV=staging --build-arg CONFIG_FILE=config.staging.jsonstaging-конфиг при тех же зависимостях
production--build-arg BUILD_ENV=production --build-arg CONFIG_FILE=config.production.jsonпрод-сборка и прод-конфиг

Имя базового образа тоже удобно выносить в аргумент, чтобы использовать один Dockerfile для разных рантаймов и архитектур. Объявление ставят до первого FROM:

ARG BASE_IMAGE=python:3.12-slim
FROM ${BASE_IMAGE} AS runtime
WORKDIR /app
COPY --from=builder /app/dist ./dist

Ограничения, о которые спотыкаются чаще всего:

  • В COPY --from имя стадии указывайте буквально: в этом флаге указывается имя стадии, из которой копируются файлы (Dockerfile reference). Подстановка переменных поддерживается лишь для ограниченного набора инструкций Dockerfile (Dockerfile reference), поэтому не рассчитывайте, что значение ARG подставится в --from автоматически.
  • Аргумент, объявленный в первой стадии, не виден во второй: объявляйте ARG в каждой стадии, где он используется.
  • Собрать отдельную стадию для отладки можно флагом --target: docker build --target build -t app:debug .

Готовые шаблоны Dockerfile под Python, Node.js и Go с multi-stage сборкой и фиксацией версий собраны в материале безопасные образы 2026: multi-stage сборка, non-root и защита от CVE.

Почему секреты нельзя хранить в ARG и ENV

Секрет, попавший в ARG или ENV, покидает вашу машину вместе с образом. Причины конкретные:

  • Значения ARG видны в истории сборки образа, поэтому их не следует использовать для секретов (Dockerfile reference).
  • Build args можно просмотреть в истории образа, поэтому их не рекомендуется использовать для секретов вроде API-ключей (Docker Build Args: What Are They and How to Use Them).
  • Значение ENV хранится в конфигурации образа, доступно через docker inspect и через env внутри контейнера.
  • Удаление файла с секретом в следующей инструкции не помогает: данные остаются в предыдущем слое, а слои неизменяемы.
  • Любой, у кого есть доступ к registry и право на pull, получает и секрет вместе с образом.

Отдельно стоит помнить про предопределённые переменные (например, прокси): по умолчанию они исключаются из вывода docker history, что снижает риск случайной утечки чувствительной аутентификационной информации (Dockerfile reference). Но это касается только встроенного набора, а не ваших собственных ARG.

Пример плохой практики: ARG DB_PASSWORD=secret и RUN cp $DB_PASSWORD /app/.env. Пароль остаётся в слое навсегда, даже если следующей строкой файл удалить. Ещё хуже ENV: команда docker inspect покажет пароль открытым текстом у любого, кто запустил контейнер.

Безопасная альтернатива для сборки - секреты BuildKit. Монтирование существует только на время одной инструкции RUN, в слой и в историю значение не попадает:

# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci --omit=dev
docker build --secret id=npmrc,src=$HOME/.npmrc -t app:1.0 .

Другие рабочие варианты: использовать секрет только в промежуточной стадии multi-stage и не копировать его в runtime-образ; передавать значения при запуске контейнера через docker run -e или secrets оркестратора; держать пароли во внешнем хранилище (Vault, AWS Secrets Manager, Yandex Lockbox) и запрашивать их приложением на старте. Дополнительно исключите .env, *.pem и ключи из контекста сборки через .dockerignore.

Полный набор мер по защите контейнеров, включая сканирование образов и запуск без root, собран в статье безопасность Docker-контейнеров 2026: чек-лист и готовые решения.

Чек-лист параметризации сборки Docker

  1. Все ARG объявлены до первого использования в RUN и COPY.
  2. Для необязательных параметров заданы значения по умолчанию, чтобы сборка без флагов давала предсказуемый результат.
  3. ARG, нужный в нескольких стадиях, объявлен в каждой из них, включая стадию, где стоит COPY.
  4. ARG стоит как можно ближе к месту использования: так смена параметра не сбрасывает кэш установки зависимостей.
  5. Секреты не передаются через --build-arg и не хранятся в ENV.
  6. Токены, ключи и данные для приватных registry передаются через --mount=type=secret с флагом --secret.
  7. Файлы с секретами исключены в .dockerignore и не копируются в образ.
  8. Сборка проверена с разными значениями ARG: dev, staging, production, и результат проверен командой docker run --rm app:1.0 env.
  9. История образа просмотрена на утечки: docker history --no-trunc app:1.0 | grep -i -E 'password|token|secret'.
  10. Все ARG перечислены в README с назначением, значением по умолчанию и примером команды сборки.

Для воспроизводимых стендов добавьте фиксацию базовых образов по digest: тег может переехать, digest - нет. Подробности и работа с lock-файлами разобраны в руководстве Docker-образы для воспроизводимых сред: dev, test и production.

Типичные ошибки, которые ломают прод

  • ARG объявлен после FROM и используется в предыдущей стадии. Значение подставляется пустым, сборка падает или собирает некорректный артефакт. Решение: объявлять ARG в каждой стадии отдельно.
  • Секрет передан через --build-arg. Пароль остаётся в истории сборки. Решение: BuildKit secrets или внешнее хранилище.
  • Параметр сборки вынесен в ENV. Значение прилипает к образу, и контейнер на staging получает прод-конфиг. Решение: параметры сборки держать в ARG, рантайм-настройки перекрывать через docker run -e.
  • ARG объявлен в середине Dockerfile перед тяжёлым RUN. Любая правка параметра обрывает кэш, и сборка идёт минуты вместо секунд. Решение: перенести объявление ниже кэшируемых шагов.
  • Ожидание подстановки ARG в COPY --from. Флаг не принимает переменную, имя стадии указывают буквально.
  • Секрет в ENV. Значение видно в docker inspect и в окружении процессов контейнера. Решение: прокидывать секрет в момент запуска или читать из внешнего хранилища.

Актуальность и версии Docker

Синтаксис ARG и ENV стабилен: инструкции появились в ранних версиях Docker и работают без изменений. Новое принесли BuildKit и сборщик buildx: монтирование секретов, проброс SSH-агента, параллельное выполнение независимых стадий и более информативный лог сборки.

В Docker Engine 23.0 Buildx и BuildKit назначены сборщиком по умолчанию на Linux, а команда docker build стала алиасом docker buildx build; легаси-сборщик по-прежнему можно использовать, явно задав DOCKER_BUILDKIT=0 (Docker Engine 23.0 release notes). Начиная с Docker Engine 23.0.0 Buildx распространяется отдельным пакетом docker-buildx-plugin, тогда как в более ранних версиях он входил в пакет docker-ce-cli (Docker Engine 23.0 release notes). Команды с --mount=type=secret и --secret требуют BuildKit, на старом сборщике они не сработают.

docker version --format '{{.Server.Version}}'

Строка # syntax=docker/dockerfile:1 в начале Dockerfile фиксирует версию фронтенда сборки и делает поведение одинаковым на локальной машине и в CI. Примеры из статьи рассчитаны на синтаксис, который поддерживают Docker 24 и новее вместе с BuildKit.

Начните с малого: вынесите в ARG версии зависимостей и имя базового образа, добавьте значения по умолчанию, прогоните сборку с флагами для staging и production, а затем проверьте историю образа на следы секретов. Чек-лист выше проходится за один вечер и снимает основную часть сюрпризов при деплое.

Поделиться: