Почему RUN apt-get update без apt-get install в той же строке — это плохо?
Отдельный RUN apt-get update кэшируется независимо: следующий install может работать с устаревшими package lists. Правило: update, install и очистка /var/lib/apt/lists/* — всегда в одной инструкции RUN.
Почему разделение — проблема кэша
Docker build cache привязывается к инструкции и её входным данным. Если RUN apt-get update вынесен в отдельную инструкцию и содержимое строки не изменилось, builder считает слой валидным и берёт из кэша. Следующий RUN apt-get install -y curl выполняется заново, но опирается на устаревшие package lists из кэшированного слоя.
Результат: в образе могут оказаться устаревшие версии пакетов, а при изменении зеркал — ошибка 404 вида E: Unable to fetch some archives. Образ при этом собирается без предупреждений.
Правильный паттерн
Объединяйте update, install и удаление кэша в одну инструкцию RUN. Это гарантирует, что package metadata и установка всегда выполняются вместе и никогда не рассинхронизируются.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
jq \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
Флаг --no-install-recommends отсекает рекомендуемые пакеты и заметно уменьшает размер слоя. Очистка /var/lib/apt/lists/* удаляет индексы, которые больше не нужны в runtime, — это снижает размер итогового image.
Когда update в отдельном слое всё же встречается
В базовых образах вроде debian:bookworm иногда делают RUN apt-get update на отдельном шаге намеренно — как отдельный base layer, который потом замораживают. Это допустимо только если за ним сразу идёт отдельный long-lived build stage и его digest закреплён. В обычных application Dockerfile такой подход неоправдан.
Подводные камни
- Добавление
--no-cacheвdocker build— не решение: это медленно, прячет проблему и не даёт кэша для CI. - Удаление
/var/lib/apt/lists/*отдельнымRUN— не помогает: списки уже записаны в предыдущий layer, слой immutable. - Версионирование пакетов:
apt-get install -y curl=7.88.*повышает воспроизводимость, но может сломаться при смене зеркала — нужен отдельный механизм контроля. - Если base image обновился и сменился apt репозиторий — ваш кэшированный
update-слой стал недействителен, но builder об этом не знает. - В multi-stage build повторяйте
update + installв каждом stage, где нужны пакеты: stage не наследует apt state. apt-get upgradeвнутри образа нежелателен — он вносит недетерминированные изменения; лучше обновить base image тег.- При использовании BuildKit cache mount (
--mount=type=cache,target=/var/cache/apt)rm -rf /var/lib/apt/lists/*не нужен в том же RUN, но кэш тогда не попадёт в image layer, а останется на builder. - Никогда не кладите
apt-get updateвONBUILD— child images будут наследовать неактуальный слой.
Common mistakes
- Разносить apt-get update, install и cleanup по разным RUN.
- Использовать docker build --no-cache как постоянный способ обхода плохого кэша.
- Очищать /var/lib/apt/lists в следующем слое и ждать уменьшения старого слоя.
What the interviewer is testing
- Понимает cache reuse для неизменившейся RUN инструкции.
- Объясняет устаревшие package lists и mirror errors.
- Пишет корректный update/install/cleanup паттерн.