Кэширование при сборке Docker-образа основано на слоях: каждая инструкция Dockerfile создает слой, и если шаг полностью совпадает с уже выполненным ранее, Docker берет этот слой из кэша.
Кэш шага валиден только при полном совпадении инструкции, всех предыдущих слоев и содержимого задействованных файлов.
Поэтому порядок инструкций критичен: редко меняющиеся шаги (базовый образ, зависимости) выносят наверх, а часто меняющийся код — в конец, чтобы при изменениях пересобирать минимум слоев.
Docker собирает образ послойно: почти каждая инструкция Dockerfile (например, FROM, RUN, COPY, ADD) создает новый слой поверх предыдущих. При повторной сборке Docker сначала пытается не выполнять шаг заново, а найти уже готовый слой в кэше.
Если подходящий слой найден, Docker просто «прикрепляет» его к результирующему образу, что делает сборку намного быстрее. Как только какой-либо шаг перестает совпадать с предыдущей сборкой, кэш для этого шага и всех последующих слоев инвалидируется, и эти шаги выполняются заново.
Кэш конкретного шага считается валидным только если одновременно выполняются несколько условий:
RUN, параметры COPY/ADD и т.п.);COPY/ADD совпадает реальное содержимое копируемых файлов в контексте сборки, а не только их имена и пути;ARG (build-аргументов), если они используются в инструкциях;--no-cache или особые политики кэширования BuildKit).Как только одно из этих условий нарушается (изменился Dockerfile, файлы в контексте, build-аргументы), Docker перестает использовать кэш с этого шага и пересобирает все последующие слои. Отсюда вытекает ключевая практика: выносить дорогие и редко меняющиеся операции (установка системных пакетов, зависимостей) как можно выше, а быстро меняющийся код приложения — как можно ниже.
Важно помнить и про контекст сборки: Docker хеширует содержимое всех файлов, которые попадают в контекст (с учетом .dockerignore). Логи, артефакты сборки или каталоги вроде node_modules, если они по ошибке попали в контекст, могут неожиданно ломать кэш шагов COPY, даже если вы их прямо не копируете.
В этом примере установка зависимостей вынесена выше, чем копирование всего исходного кода. Если вы меняете только код (файлы, кроме package*.json), Docker повторно использует кэш для шагов COPY package*.json ./ и RUN npm ci, а пересобирает только слой после второго COPY. Но если изменится package.json, то кэш слоя с установкой зависимостей будет признан невалидным, и шаг RUN npm ci выполнится заново.
Во втором примере дорогие шаги (apt-get install и установка Python-зависимостей) стоят до копирования основного кода. Благодаря этому при изменении только исходников пересобираются лишь последние слои, а тяжелые операции остаются в кэше. Для полного отключения кэша при необходимости можно использовать флаг docker build --no-cache или продвинутые настройки BuildKit, но в повседневной разработке правильная структура Dockerfile даёт основной выигрыш.
Отметьте свой прогресс