Как Docker узнает, что слой образа можно не пересобирать
Что проверяют
Понимаете ли вы, как устроен кэш сборки, и умеете ли писать Dockerfile, который не ставит зависимости заново после правки одной строки кода. Следом почти всегда просят показать правильный порядок инструкций для Python-проекта.
Как отвечать
Сборкой сейчас занимается BuildKit, он включён по умолчанию с Docker Engine 23. Он идёт по инструкциям сверху вниз и для каждой ищет в кэше результат с тем же ключом. Ключ складывается из родительского слоя и самой инструкции:
RUN: сравнивается только текст команды и окружение, включая использованныеARG. Что команда скачает из сети, Docker не проверяет, поэтомуRUN pip install requestsвозьмётся из кэша, даже если вышла новая версия пакета;COPYиADD: считается контрольная сумма содержимого копируемых файлов. Время изменения файла не учитывается, важны байты;FROM: берётся локальный базовый образ, пока не передадите--pull.
Главное правило: промах кэша на одной инструкции инвалидирует все последующие. Отсюда порядок для Python:
COPY pyproject.toml uv.lock ./
RUN --mount=type=cache,target=/root/.cache/uv uv sync --frozen --no-install-project
COPY . .Сначала файлы зависимостей и установка, код в самом конце. Правка в app.py пересобирает только последний слой. Cache mount вдобавок сохраняет скачанные пакеты между сборками, даже когда слой всё-таки пересобирается. С pip схема та же: requirements.txt и /root/.cache/pip.
Упомяните .dockerignore: без него в COPY . . попадут .git, .venv и __pycache__, и контрольная сумма будет меняться постоянно.
Где спотыкаются
Думают, что Docker сравнивает результат RUN. Классика: RUN apt-get update закэшировался месяц назад, и свежие версии пакетов не приезжают. Помогает объединить update и install в одну инструкцию или собрать с --no-cache.
Пишут COPY . . перед установкой зависимостей, и любая правка кода запускает её заново.
Забывают, что кэш локальный. На чистом CI-раннере его нет, нужны --cache-from и --cache-to у docker buildx build.