ARG задаёт значение только на время сборки, ENV остаётся в образе и попадает в запущенный контейнер. Практическое правило: версии зависимостей, имена базовых образов и флаги сборки передавайте через ARG и --build-arg, а настройки, которые нужны процессу в рантайме, храните в ENV или передавайте при запуске контейнера.
Ошибка в выборе инструкции даёт два типовых сбоя: образ на стенде ведёт себя иначе, чем ожидалось, либо пароль из флага сборки остаётся в истории образа и доступен любому, кто этот образ скачал. Разбираем область видимости, влияние на кэш, готовые команды и чек-лист.
ARG и ENV в Dockerfile: ключевые отличия
Сравнение по признакам, которые чаще всего нужны при отладке сборки:
| Критерий | ARG | ENV |
|---|---|---|
| Когда доступен | На этапе сборки, в инструкциях после объявления | На этапе сборки и в запущенном контейнере |
| Область видимости | Одна стадия сборки; до первого FROM работает глобально | Все последующие инструкции стадии и дочерние образы |
| Значение по умолчанию | ARG VERSION=1.0 | ENV APP_ENV=prod |
| Переопределение | docker build --build-arg VERSION=2.0 | docker 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.json | dev-сборка приложения и dev-конфиг |
| staging | --build-arg BUILD_ENV=staging --build-arg CONFIG_FILE=config.staging.json | staging-конфиг при тех же зависимостях |
| 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
- Все ARG объявлены до первого использования в RUN и COPY.
- Для необязательных параметров заданы значения по умолчанию, чтобы сборка без флагов давала предсказуемый результат.
- ARG, нужный в нескольких стадиях, объявлен в каждой из них, включая стадию, где стоит COPY.
- ARG стоит как можно ближе к месту использования: так смена параметра не сбрасывает кэш установки зависимостей.
- Секреты не передаются через --build-arg и не хранятся в ENV.
- Токены, ключи и данные для приватных registry передаются через --mount=type=secret с флагом --secret.
- Файлы с секретами исключены в .dockerignore и не копируются в образ.
- Сборка проверена с разными значениями ARG: dev, staging, production, и результат проверен командой docker run --rm app:1.0 env.
- История образа просмотрена на утечки: docker history --no-trunc app:1.0 | grep -i -E 'password|token|secret'.
- Все 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, а затем проверьте историю образа на следы секретов. Чек-лист выше проходится за один вечер и снимает основную часть сюрпризов при деплое.