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-командах ставишь разное.
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
Что происходит при первой сборке:
- BuildKit монтирует пустую кэш-директорию как
/root/.cache/pipвнутри RUN. pip installкачает пакеты и пишет кэш в/root/.cache/pip— это пишется в persistent volume BuildKit, не в слой образа.- После RUN кэш-mount размонтируется. Файлы кэша не попадают в слой.
Что при второй сборке (изменился main.py, но requirements.txt прежний):
- RUN
pip installзапускается заново (потому что хеш контекста изменился из-заmain.py— хотя на самом деле даже не запускается из-за layer cache). - Если RUN всё же запустится (например, поменялся pip),
/root/.cache/pip— это тот же persistent volume, всё с прошлого раза там. pip installнаходит wheel в кэше, ставит за секунды.
Что при третьей сборке (добавили зависимость в requirements.txt):
- Layer cache инвалидирован (хеш файла изменился), RUN запускается.
- Кэш-mount монтируется с прошлым содержимым.
- Pip уже знает большинство пакетов (они с прошлого раза в кэше), качает только новую.
Выигрыш: первая сборка такая же, повторные — в десятки раз быстрее. Финальный слой при этом БЕЗ кэша.
Сравнение: numbers
Тест на проекте с airflow + pandas + numpy + psycopg + sqlalchemy (~25 зависимостей):
| Подход | Первая сборка | Ребилд после правки кода | Ребилд после добавления 1 пакета | Финальный образ |
|---|---|---|---|---|
pip install (с кэшем в слое) | 60 сек | 60 сек (если cache miss) или 0 сек (cache hit) | 60 сек | 480 МБ |
pip install --no-cache-dir | 60 сек | 60 сек или 0 сек | 60 сек | 400 МБ |
BuildKit --mount=type=cache | 60 сек | 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 минута.
Попробуй сам
- Создай простой Dockerfile с
pandas + sqlalchemy + airflow:FROM python:3.13-slim COPY requirements.txt . RUN pip install -r requirements.txtrequirements.txt: pandas, sqlalchemy, apache-airflow. - Замерь время сборки:
time docker build -t v1 .. Замерь размер:docker images v1. - Добавь
--no-cache-dir, пересобери. Размер должен упасть. - Перепиши на 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.txttime docker build -t v2 .. Размер? - Добавь
requestsв requirements. Пересобери. Запиши время — должно быть гораздо меньше, чем первая сборка. - Очисти BuildKit cache (
docker buildx prune), пересобери. Время вернётся к первой сборке.