Docker COPY не видит файл: контекст сборки, путь Dockerfile и .dockerignore

Сборка падает на COPY ../shared/config.yaml, хотя файл виден в проводнике. Путь Dockerfile и корень доступных сборщику файлов — разные вещи. Последний аргумент docker build задаёт контекст. COPY читает из этого контекста, а не из произвольного каталога вокруг Dockerfile. Проверим границу на небольшом дереве, не меняя порядок слоёв ради кэша.

Восстанавливаем команду и дерево проекта

Пусть в репозитории есть такой набор файлов. Команды ниже выполняются из его корня:

project/
  deploy/
    Dockerfile
  app/
    main.py
  shared/
    config.yaml
  .dockerignore
docker build -f deploy/Dockerfile -t context-lab .

Точка задаёт context project. Флаг -f выбирает deploy/Dockerfile, но не переносит context в deploy. Поэтому инструкция COPY shared/config.yaml /app/config.yaml обращается к project/shared/config.yaml. Если запустить docker build -f Dockerfile . из deploy, context станет другим и shared окажется снаружи, хотя инструкция выглядит похожей.

Делаем пример без внешнего base image

Для проверки доступности файла достаточно образа scratch. Содержимое deploy/Dockerfile:

FROM scratch
COPY shared/config.yaml /app/config.yaml

Он создаёт файловый образ без команды запуска; запускать его не требуется. В shared/config.yaml запишите mode: demo. Успешная сборка подтверждает, что указанный файл вошёл в context и не был исключён. Попытка COPY ../shared/config.yaml не является способом читать родительский каталог за пределами context: навигация к родителю не расширяет разрешённый набор файлов.

Такая ошибка часто встречается в монорепозитории, где Dockerfile лежит в services/api, а общая библиотека — в shared. Задайте корень репозитория как context и скорректируйте COPY относительно него. Не переносите секретный или огромный корень файловой системы в context только ради одного файла. Лучше выбрать минимальный общий каталог или осознанно использовать named context.

Проверяем фильтрацию .dockerignore

.dockerignore в корне context удаляет совпавшие пути из набора файлов перед передачей сборщику. Если там есть shared/, COPY не увидит файл, даже когда локально он существует. Сравните регистр и имя: на Linux config.yaml и Config.yaml отличаются, хотя локальная файловая система могла прощать различие.

**
!shared/
!shared/config.yaml

Это минимальный пример исключения почти всего с явным возвратом каталога и файла. Порядок правил с ! имеет значение: последнее подходящее правило определяет результат. Проверьте файл, а не только наличие строки с его именем. Dockerfile и ignore-файл имеют особое назначение при передаче, но это не означает, что их можно затем произвольно COPY при исключении.

Рядом с конкретным Dockerfile может лежать Dockerfile-specific ignore: для deploy/Dockerfile имя будет deploy/Dockerfile.dockerignore. Такой файл имеет приоритет над корневым .dockerignore. При расследовании нужно найти оба, иначе исправление общего ignore никак не изменит используемый фильтр. Это особенно заметно, когда тестовая и рабочая сборки используют разные Dockerfile.

Сверяем Compose и CI

Если сборка запускается через Compose, проверьте build.context и build.dockerfile в итоговой модели:

services:
  api:
    build:
      context: .
      dockerfile: deploy/Dockerfile

Контекст здесь задаётся относительно расположения Compose-конфигурации с учётом правил нескольких файлов. Dockerfile ищется относительно context. В CI проверьте каталог checkout и рабочую директорию шага: та же точка в конце команды может обозначать другой набор файлов. Отдельно убедитесь, что нужный файл вообще появился после checkout или предыдущего этапа генерации.

Подтверждаем причину исправления

Запустите учебную сборку с явным -f и context, затем сравните ошибочный вызов из другого каталога. Не используйте --no-cache как основное лечение not found: он меняет повторное использование слоёв, но не расширяет доступ к файлам. Размер переданного context полезен как подсказка о фильтрации, однако сам по себе не доказывает наличие конкретного файла.

Сохраните в инструкции проекта одну команду с обозначенной рабочей директорией и объясните основание путей COPY. Тогда новый разработчик сможет собрать тот же набор файлов без догадок о расположении Dockerfile. Исправлением считается воспроизводимая граница входных данных сборки, а не случайно успешный запуск из одного терминала.

Ещё по теме

Продолжить изучение

Хотите разобраться в контейнеризации целиком, от образа до работающего окружения? Сборка, сети, данные и запуск приложений входят в программу «Docker и Docker Compose: контейнеризация приложений».

Продолжить на практике

  • Docker и Docker Compose: контейнеризация приложений ↗

    Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.