DockerMiddleCoding

Почему 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 паттерн.

Sources

Related topics