Короткий ответ
Docker кэширует каждый слой по хешу инструкции и её входных данных; если слой и все предыдущие совпадают с кэшем, сборка переиспользует готовый слой без повторного выполнения — поэтому редко меняющиеся шаги (установку зависимостей) нужно располагать раньше часто меняющегося кода.
Как это работает подробнее
для каждой инструкции сборщик вычисляет ключ кэша на основе самой команды и (для COPY/ADD) содержимого копируемых файлов. Если ключ совпадает с ранее сохранённым слоем и все предыдущие слои тоже были взяты из кэша, инструкция не выполняется повторно — используется готовый слой. Как только один слой в цепочке меняется, все последующие слои инвалидируются и пересобираются с нуля, даже если их входные данные не менялись.
Пример кода
Dockerfile
FROM node:20
COPY . .
RUN npm install
CMD ["npm", "start"]
FROM node:20
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "start"]Что использовать на практике
- Практика. классический паттерн для Node.js/Python/Go — сначала копировать только файл с описанием зависимостей (
package.json,requirements.txt,go.mod), выполнить установку зависимостей, и только потом копировать весь исходный код. Тогда изменение кода приложения не инвалидирует дорогой шаг установки зависимостей.
Подводные камни
- Риск. пишут
COPY . .сразу в начале Dockerfile перед установкой зависимостей — любое изменение любого файла в репозитории (даже README) инвалидирует кэш установки зависимостей, и она выполняется заново при каждой сборке; используют BuildKit cache mount некорректно, полагая, что он заменяет правильный порядок инструкций.