Приватный пакет, который на вашей машине разрешается без проблем, может не пройти внутри сборки с ошибкой 404 или по тайм-ауту, и Dockerfile редко бывает причиной. Шаг сборки выполняется в другом сетевом пространстве имён, с другим представлением DNS и другим хранилищем учётных данных, чем оболочка, в которой вы тестировали. Пройдите эти проверки по порядку и остановитесь на первой неудачной, потому что каждая следующая предполагает, что предыдущая прошла. Каждый шаг называет команду, которая решает вопрос.

Начните с имени builder, а не с Dockerfile

BuildKit может быть локальным, работать в контейнере или на другой машине, и у каждого своя сеть по умолчанию. Выполните docker buildx ls и прочитайте узел, помеченный звёздочкой, прежде чем что-либо менять. Один docker-container драйвер или облачный builder не разделяет приватный маршрут на вашем ноутбуке только потому, что вы набрали команду там. Если активный builder удалённый, каждая оставшаяся проверка должна выполняться на этом хосте, а не на вашей рабочей станции.

Builder несёт и свою конфигурацию resolver и переменные proxy. Обе читаются во время сборки и не наследуются от терминала, который её запустил.

Докажите сетевой путь изнутри шага сборки

Напишите одноразовый этап, который разрешает хост зависимости и открывает к нему соединение. Это единственный тест, который отвечает на вопрос за builder, а не за вашу оболочку.

FROM alpine:3.20
RUN apk add --no-cache curl bind-tools  && getent hosts registry.example.internal  && curl -sS -o /dev/null -w "%{http_code}
" https://registry.example.internal/simple/

Соберите этот файл тем же builder, сетевым режимом и теми же монтированиями секретов, что и ваша настоящая сборка: docker buildx build --builder remote0 -f Dockerfile.probe --progress=plain .. Если getent ничего не выводит, неисправность в разрешении имён и никакие учётные данные её не исправят. Если имя разрешается, но curl сообщает о тайм-ауте, препятствие — маршрут или политика выхода перед реестром.

Удалите зонд, как только он ответит. Это диагностический этап, и его никогда не следует помечать тегом или отправлять.

Монтируйте учётные данные, а не копируйте их

Когда путь заработает, следующая по очереди неудача — аутентификация. Монтирование секрета BuildKit открывает файл одному RUN шагу, не записывая его в слой; именно это нужно инструменту зависимостей и именно этого образ не должен сохранять.

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc     npm ci --omit=dev

Секрет должен подаваться извне Dockerfile: docker buildx build --secret id=npmrc,src=/home/me/.npmrc .. Файл остаётся у клиента и никогда не попадает в историю образа. Ни ARG ни ENV с тем же токеном не выполняют эту работу, потому что оба записываются в конфигурацию образа.

Образу без root нужен собственный целевой путь, например /home/app/.npmrc или эквивалентный файл для pip, Maven или Cargo. Цель монтирования должна совпадать с пользователем, который выполняет команду, иначе инструмент сообщит об отсутствующем файле конфигурации, который на самом деле есть.

Проверьте, что инструмент зависимостей оставляет в образе

Временное монтирование держит учётные данные вне слоя, но не мешает инструменту записать что-то другое. Менеджеры пакетов оставляют lock-файлы, каталоги кеша и иногда разрешённый адрес реестра.

Читайте готовую файловую систему, а не доверяйте Dockerfile. docker history --no-trunc image:tag перечисляет каждую команду слоя, а docker run --rm image:tag sh -c "ls -a /root" показывает, что уцелело в работающем контейнере. Даже если файл был только смонтирован, токен, записанный в строку журнала во время npm ci всё равно остаётся утечкой.

Прочитайте и журнал сборки, потому что --progress=plain печатает вывод команд дословно. Если токен появится там, смените его раньше всего остального и передавайте через файл, который инструмент читает молча.

Убедитесь, что рабочему образу больше не нужен приватный путь

Сборка и работающее приложение — два разных потребителя одной зависимости, и приватный маршрут нужен только первому. Готовый образ должен запускаться, разрешать свои рабочие зависимости и обслуживать трафик без учётных данных сборки.

Проверьте это там, где приватный реестр недоступен, или просто запустите контейнер с --network none и убедитесь, что он поднимается без смонтированного секрета. Контейнер, который запускается только при наличии секрета сборки, всё ещё связан со средой сборки.

Если контейнеру действительно нужен реестр во время работы, это отдельное решение о доступе, и его место — в хранилище секретов оркестратора, а не в сборке. Опишите два пути отдельно, чтобы никто не пытался починить загрузку во время работы флагом сборки. Когда все пять проверок пройдены, у исходного симптома есть названная причина на каждом уровне вместо догадки о сети Docker.

Похожие статьи