Learning Platform
Глоссарий Troubleshooting
Урок 17.02 · 22 мин
Средний
dockerpythonbuildkitcache

pip cache: —no-cache-dir vs BuildKit

При сборке Python-образа pip install качает пакеты с PyPI, кэширует их в ~/.cache/pip/, потом устанавливает в site-packages. Кэш — это десятки/сотни МБ скачанных wheel-файлов. Для runtime’а этот кэш бесполезен (приложение работает с установленными пакетами в site-packages, не с кэшем). Но для повторных сборок он золотой — повторный pip install pandas берёт wheel из кэша, не качает с PyPI.

В этом уроке: как использовать --no-cache-dir чтобы не раздувать образ, и как через BuildKit получить и маленький образ и быстрый rebuild.


open(), read(), write() — системные вызовы для файлов

Проблема: cache раздувает слой

Стандартный Dockerfile:

FROM python:3.13-slim
COPY requirements.txt .
RUN pip install -r requirements.txt

После pip install pandas:

  • Установлены пакеты в /usr/local/lib/python3.13/site-packages/ (~200 МБ для pandas + зависимости).
  • Закэшированы wheel-файлы в /root/.cache/pip/ (~80 МБ).

Финальный слой = 280 МБ. Кэш не нужен в runtime, но он в слое.

Стандартный фикс — --no-cache-dir:

FROM python:3.13-slim
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

Pip не пишет кэш на диск, сразу удаляет скачанные wheel’ы после установки. Слой = 200 МБ.

Альтернатива — env-переменная PIP_NO_CACHE_DIR=1 на уровень всего образа:

FROM python:3.13-slim
ENV PIP_NO_CACHE_DIR=1
RUN pip install -r requirements.txt

Любой pip install будет без кэша. Удобно, если в нескольких RUN-командах ставишь разное.

TIP

hadolint правило DL3042 проверяет именно это: Avoid the use of cache directory with pip. Если видишь в линте — фикси на --no-cache-dir.


Цена --no-cache-dir: медленный rebuild

Когда меняешь requirements.txt и пересобираешь — pip качает все пакеты с PyPI заново. На pandas + airflow + numpy + scipy это 100-300 МБ загрузки, 2-5 минут.

Возникает дилемма:

  • С кэшем: образ толстый (надежно лежит лишний 80 МБ), но повторная сборка быстрая.
  • Без кэша: образ тонкий, повторная сборка медленная (всё качается).

До BuildKit это был реальный trade-off. С BuildKit — нет.


BuildKit --mount=type=cache

BuildKit — современный сборщик образов от Docker (default с 23.0). Он поддерживает cache mounts — кэш-директории, которые персистентны между сборками, но не попадают в финальный слой.

# syntax=docker/dockerfile:1.7
FROM python:3.13-slim
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

Что происходит при первой сборке:

  1. BuildKit монтирует пустую кэш-директорию как /root/.cache/pip внутри RUN.
  2. pip install качает пакеты и пишет кэш в /root/.cache/pip — это пишется в persistent volume BuildKit, не в слой образа.
  3. После RUN кэш-mount размонтируется. Файлы кэша не попадают в слой.

Что при второй сборке (изменился main.py, но requirements.txt прежний):

  1. RUN pip install запускается заново (потому что хеш контекста изменился из-за main.py — хотя на самом деле даже не запускается из-за layer cache).
  2. Если RUN всё же запустится (например, поменялся pip), /root/.cache/pip — это тот же persistent volume, всё с прошлого раза там.
  3. pip install находит wheel в кэше, ставит за секунды.

Что при третьей сборке (добавили зависимость в requirements.txt):

  1. Layer cache инвалидирован (хеш файла изменился), RUN запускается.
  2. Кэш-mount монтируется с прошлым содержимым.
  3. Pip уже знает большинство пакетов (они с прошлого раза в кэше), качает только новую.
BuildKit cache mount: pip install
Первая сборкаКэш-volume пуст, pip качает всё с PyPI и пишет в кэш
2 мин
образ 200 МБФинальный слой без кэша — только site-packages
Ребилд после изменения main.pyrequirements.txt тот же — layer cache hits, RUN не выполняется
5 сек
образ 200 МБТот же финальный слой
Ребилд после добавления зависимостиrequirements.txt изменился, layer cache miss, RUN выполняется. Но кэш-volume с прошлой сборкой!
10 сек
образ 200 МБPip нашёл известные пакеты в кэше, скачал только новую зависимость

Выигрыш: первая сборка такая же, повторные — в десятки раз быстрее. Финальный слой при этом БЕЗ кэша.


Сравнение: numbers

Тест на проекте с airflow + pandas + numpy + psycopg + sqlalchemy (~25 зависимостей):

ПодходПервая сборкаРебилд после правки кодаРебилд после добавления 1 пакетаФинальный образ
pip install (с кэшем в слое)60 сек60 сек (если cache miss) или 0 сек (cache hit)60 сек480 МБ
pip install --no-cache-dir60 сек60 сек или 0 сек60 сек400 МБ
BuildKit --mount=type=cache60 сек0 сек (cache hit)5-10 сек400 МБ

С BuildKit получаем размер --no-cache-dir И скорость кэшированного варианта.


Включение BuildKit

В Docker 23+ BuildKit — default. Проверить:

docker version | grep -i buildkit

Если хочешь явно:

DOCKER_BUILDKIT=1 docker build -t my-image .

Или через buildx:

docker buildx build -t my-image .

В Compose v2 BuildKit активен из коробки.

Синтаксис # syntax=

В Dockerfile в самом верху:

# syntax=docker/dockerfile:1.7
FROM python:3.13-slim
...

Это указывает версию Dockerfile frontend. Без этого --mount=type=cache может работать (BuildKit умный), но лучше явно. Версия 1.7 (середина 2026) поддерживает все актуальные features: secrets, ssh, cache mounts, heredoc.


Cache mount опции

RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
    pip install -r requirements.txt

Опции:

  • target=/path — куда монтировать в контейнере. Должна быть директорией, в которую инструмент пишет кэш.
  • id=<name> — идентификатор кэша. Можно делиться кэшом между разными билдами/проектами через одинаковый id.
  • sharing=shared|locked|private — режим конкурентного доступа. locked — только один builder одновременно (для pip OK). shared — могут параллельно (хорошо для read-mostly кэшей). private — каждый билд свой.
  • mode=0755 — права на кэш-директорию.
  • uid=1000,gid=1000 — владелец кэш-директории. Если RUN от non-root.

Типичный для pip:

RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
    pip install --no-cache-dir -r requirements.txt

Это парадокс: --no-cache-dir И cache mount. Дело в том, что --no-cache-dir для pip означает «не использовать кэш этой инвокации». А cache mount даёт ~/.cache/pip как директорию между сборками. Pip всё равно её использует для скачивания wheel’ов, но не сохраняет новые туда. На практике для скорости лучше без --no-cache-dir при наличии cache mount.

Простая модель: убираешь --no-cache-dir, добавляешь cache mount, всё работает.

# syntax=docker/dockerfile:1.7
FROM python:3.13-slim
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

Где живёт BuildKit cache

На macOS / Linux:

docker buildx du

Покажет размер всех кэшей. Очистить:

docker buildx prune --filter type=exec.cachemount

Или совсем всё:

docker buildx prune --all

В CI можно настроить --cache-to=type=registry,ref=my-registry/cache — это сохранит кэш в registry, и следующий CI run его подтянет. Это меняет дело — повторные CI прогоны становятся очень быстрыми.

docker buildx build \
  --cache-to=type=registry,ref=registry.example.com/cache:latest,mode=max \
  --cache-from=type=registry,ref=registry.example.com/cache:latest \
  -t my-image:${TAG} .

Pattern: pip install в одном RUN

ВАЖНО: pip cache mount не имеет смысла, если pip install разбит на несколько RUN:

# BAD — каждый RUN свой layer cache miss
RUN --mount=type=cache,target=/root/.cache/pip pip install pandas
RUN --mount=type=cache,target=/root/.cache/pip pip install psycopg

Лучше — один requirements.txt, один RUN:

# GOOD
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt

Это даёт максимальный layer reuse: если requirements.txt не менялся — RUN вообще не выполняется (layer cache hit).


Order matters: requirements.txt отдельно от кода

Не пиши:

# BAD — любое изменение кода ломает layer cache для pip install
COPY . .
RUN pip install -r requirements.txt

Пиши:

# GOOD — requirements.txt копируется первым, pip install кэшируется отдельно от кода
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

Любое изменение в main.py инвалидирует только последний слой (COPY . .), pip install остаётся в кэше. Это основной принцип Docker layer caching (см. модуль 7).

В сочетании с BuildKit cache:

# syntax=docker/dockerfile:1.7
FROM python:3.13-slim

WORKDIR /app

COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt

COPY . .

CMD ["python", "main.py"]

— это production-grade паттерн. Меняешь main.py -> ребилд за 5 секунд. Добавляешь зависимость -> ребилд за 10 секунд. Чистая сборка -> 1 минута.

Layer cache + BuildKit cache mount
COPY requirements.txtСлой кэшируется если файл не менялся — layer cache. Стандартный Docker механизм
RUN pip installЕсли COPY кэшировался — RUN тоже кэшируется (тот же hash). Слой готов из истории
изменили requirements.txtХеш слоя изменился, layer cache miss
RUN запускается, кэш-mount горячийPip качает только новую зависимость, остальные находит в /root/.cache/pip из прошлой сборки
COPY . .Изменения в main.py меняют этот слой, но НЕ pip install
ребилд 5 секундТолько последний слой пересобирается, pip install reused from cache

Попробуй сам

  1. Создай простой Dockerfile с pandas + sqlalchemy + airflow:
    FROM python:3.13-slim
    COPY requirements.txt .
    RUN pip install -r requirements.txt
    requirements.txt: pandas, sqlalchemy, apache-airflow.
  2. Замерь время сборки: time docker build -t v1 .. Замерь размер: docker images v1.
  3. Добавь --no-cache-dir, пересобери. Размер должен упасть.
  4. Перепиши на BuildKit cache:
    # syntax=docker/dockerfile:1.7
    FROM python:3.13-slim
    COPY requirements.txt .
    RUN --mount=type=cache,target=/root/.cache/pip \
        pip install -r requirements.txt
    Первая сборка — time docker build -t v2 .. Размер?
  5. Добавь requests в requirements. Пересобери. Запиши время — должно быть гораздо меньше, чем первая сборка.
  6. Очисти BuildKit cache (docker buildx prune), пересобери. Время вернётся к первой сборке.

Проверка знанийKnowledge check
Объясни разницу между pip install --no-cache-dir и BuildKit --mount=type=cache,target=/root/.cache/pip — какие проблемы решает каждый, и почему BuildKit подход лучше для DE-проектов?
ОтветAnswer
--no-cache-dir говорит pip-у НЕ создавать кеш скачанных wheels в /root/.cache/pip/. Без этого флага pip скачивает wheels, кеширует на диск, потом устанавливает. Кеш остаётся в слое образа (десятки МБ), хотя для runtime не нужен (приложение работает с установленными пакетами в site-packages, не с кешем). --no-cache-dir держит слой тонким (минус 80 МБ для проекта с pandas), но каждая пересборка качает всё с PyPI заново — медленно. BuildKit --mount=type=cache,target=/root/.cache/pip — другая логика. Этот mount создаёт persistent volume вне образа, который монтируется в /root/.cache/pip ТОЛЬКО во время RUN-инструкции. Pip пишет в него кеш как обычно, но: - кеш сохраняется между сборками (volume не удаляется), - файлы кеша НЕ попадают в финальный слой образа (mount размонтируется после RUN). Результат: получаешь и тонкий образ (как с --no-cache-dir), и быстрые ребилды (как с обычным cache). Первая сборка такая же — pip качает с PyPI. Вторая сборка после изменения requirements.txt — pip находит знакомые пакеты в persistent cache, качает только новые. Пересборка после правки main.py — Docker layer cache, pip install вообще не выполняется. Для DE-проектов с pandas/numpy/scipy/airflow это разница между «ребилд 2 минуты» и «ребилд 10 секунд» — критично для производительности разработчика и скорости CI. Production-pattern: # syntax=docker/dockerfile:1.7 в начале, COPY requirements.txt отдельно от кода, RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt, потом COPY . .. Это даёт максимум reuse layer cache и BuildKit cache одновременно.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. В Dockerfile: RUN pip install -r requirements.txt (без --no-cache-dir, без BuildKit cache mount). Что попадает в слой образа?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5