Короткий ответ
Образ нужно собирать один раз, а окружение-специфичную конфигурацию внедрять при запуске (через переменные окружения, конфигурационные файлы/ConfigMap), а не пересобирать образ для каждого окружения; build-time секреты (например, токен приватного npm registry) передавать через механизм временного монтирования секрета в BuildKit (--secret), а не через ARG, значения которого остаются в истории слоёв образа.
Как это работает подробнее
ARG, даже если не зафиксирован явной инструкцией ENV, всё равно виден в метаданных промежуточных слоёв образа и может быть восстановлен через docker history с флагом, показывающим полные команды, либо через анализ самого манифеста — то есть секрет, переданный через ARG, нельзя считать надёжно удалённым из артефакта. BuildKit secret mount (RUN --mount=type=secret,id=...) предоставляет секрет процессу сборки только во время выполнения конкретной RUN-инструкции, через файл, который не фиксируется в итоговом слое.
Пример кода
RUN --mount=type=secret,id=npmrc \
cp /run/secrets/npmrc /root/.npmrc && \
npm install && \
rm /root/.npmrcЧто использовать на практике
- Практика. "build once, promote everywhere" подразумевает, что один и тот же digest образа тестируется в dev/staging и затем продвигается (promote) в prod без пересборки — это даёт сильную гарантию, что протестированный артефакт и есть то, что работает в проде; вся разница между окружениями должна быть вынесена за пределы образа, в runtime-конфигурацию.
Подводные камни
- Риск. пересобирают образ отдельно для каждого окружения с разными переменными, "зашитыми" через
ARG/ENVна этапе сборки, теряя гарантию, что в проде работает именно тот артефакт, который тестировался в staging; передают секреты сборки черезARG, считая, что.dockerignoreили удаление файла послеRUNрешает проблему — метаданные слоя всё равно могут хранить переданное значение.