Learning Platform
Глоссарий Troubleshooting
Урок 09.02 · 24 мин
Средний
dockerbuildkitcachesecretsbuild-performance

BuildKit и cache mounts: ускорение сборки

BuildKit это новый backend для docker build, который заменил legacy builder начиная с Docker 23 (default с 23.0, обязателен с 28). Он принёс параллельность сборок stages, лучший cache и принципиально новые возможности: RUN --mount для cache (pip / npm / apt) и secrets. Это превратило сборки из «pull зависимостей каждый build» в «pull один раз, потом мгновенно».

В этом уроке: что нового BuildKit, как использовать cache mounts для pip и npm, и как монтировать secrets без утечки в слои.


Что такое BuildKit

BuildKit это:

  • Параллельная сборка stages — multi-stage stages, не зависящие друг от друга, собираются параллельно
  • Continued building при ошибке — если падает один stage, BuildKit продолжает собирать те, что не зависят от него
  • Лучший cache — content-aware hashing, поддержка remote cache backends
  • Frontends — Dockerfile это один из «языков» для BuildKit, можно использовать другие (HCL, Earthly и т.п.)
  • RUN --mount — типа cache, типа secret, типа bind, типа ssh
  • Multi-platform buildsdocker buildx для linux/amd64 + linux/arm64 одновременно

Проверить что используется:

$ docker version
Server: Docker Engine - Community
 Engine:
  Version:          28.0.0
  ...
  builder:
    Version:        v0.26.0     # BuildKit

В современном Docker (23+) BuildKit это default. На очень старых versions нужно было DOCKER_BUILDKIT=1 docker build или включить в /etc/docker/daemon.json.


Mount, /etc/fstab и mount namespaces

RUN —mount=type=cache: персистентный cache между builds

Базовая проблема: при docker build каждый раз pip install скачивает пакеты с PyPI. Если кэш ~/.cache/pip сохраняется внутри образа — он раздувает слой; если очищается через --no-cache-dir или RM — теряем скорость на repeated builds.

RUN --mount=type=cache решает эту проблему:

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"]

Что происходит:

  1. BuildKit создаёт persistent cache volume (не часть образа), смонтированный в /root/.cache/pip во время выполнения этого RUN.
  2. pip качает пакеты, складывает в /root/.cache/pip.
  3. После RUN cache volume отмонтируется, в слой попадает только результат pip install (установленные packages в site-packages).
  4. При следующем build: тот же cache volume монтируется снова, pip видит уже скачанные wheels — install мгновенный.
$ docker build -t app:v1 .
[+] Building 45.5s    # первый раз: качаем pandas, numpy, etc

# Меняем код, не requirements.txt
$ docker build -t app:v1 .
[+] Building 1.8s     # CACHED

# Меняем requirements.txt -- добавили новую зависимость
$ docker build -t app:v1 .
[+] Building 3.2s     # только новая зависимость качается, остальные из cache mount

Cache живёт между builds, в /var/lib/docker/buildkit/cache-volumes/. Не попадает в финальный образ. Можно очистить через docker builder prune.

Cache mount: pip wheels between builds
First build: download allПервый build: pip качает все пакеты с PyPI, складывает в cache mount /root/.cache/pip.
cache populated
cache volumeBuildKit сохраняет cache в /var/lib/docker/buildkit/. Не часть образа.
Next build: reuseВторой build (requirements изменился): cache mount уже заполнен, pip переиспользует wheels вместо download.
fast
~3s installТолько новые пакеты качаются, остальные мгновенно.

Cache mounts для разных языков

pip (Python):

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

npm (Node.js):

RUN --mount=type=cache,target=/root/.npm \
    npm install

pnpm:

RUN --mount=type=cache,target=/pnpm/store \
    pnpm install --frozen-lockfile

Go (modules):

RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build ./...

apt:

RUN --mount=type=cache,target=/var/cache/apt \
    --mount=type=cache,target=/var/lib/apt \
    apt-get update && apt-get install -y libpq5

Для apt нужно ещё одно: убрать apt-get clean и не делать rm -rf /var/lib/apt/lists/* (это противоречит тому, что было в модуле 7 для single-stage; здесь apt-cache живёт в cache mount, не в слое). Также важно убрать default /etc/apt/apt.conf.d/docker-clean или поставить Acquire::http::No-Cache "false";.

Maven:

RUN --mount=type=cache,target=/root/.m2 \
    mvn package

Параметры cache mount

RUN --mount=type=cache,target=/path,id=unique-id,sharing=locked,mode=0755,uid=1000,gid=1000 \
    command
  • target — путь внутри контейнера build’а
  • id — уникальный ID cache volume (default = target). Используй чтобы шарить cache между Dockerfile’ами, либо изолировать.
  • sharingshared (default, конкурентные builds могут одновременно работать с cache), private (каждый build создаёт свой, новый, копию), locked (один build за раз)
  • mode — permissions cache mount (default 0755)
  • uid, gid — owner если внутри Dockerfile USER не root

Pin id для shared cache между разными Dockerfile’ами:

# Dockerfile.web
RUN --mount=type=cache,id=npm-cache,target=/root/.npm \
    npm install

# Dockerfile.api
RUN --mount=type=cache,id=npm-cache,target=/root/.npm \
    npm install

Оба Dockerfile шарят cache по id npm-cache. Если в проекте web/ и api/ имеют общие npm-deps — cache переиспользуется.


RUN —mount=type=secret: secrets без утечки

До BuildKit secrets было сложно: --build-arg DB_PASSWORD=secret запинит секрет в image history навечно. Распространённые workarounds (multi-stage с throwaway копированием) сложны.

--mount=type=secret решает это:

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

COPY requirements.txt .

# Доступ к private PyPI с auth
RUN --mount=type=secret,id=pypi_auth \
    pip install --index-url https://$(cat /run/secrets/pypi_auth)@pypi.private.com/simple -r requirements.txt

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

Build:

docker build \
    --secret id=pypi_auth,src=$HOME/.pypi-auth \
    -t app:v1 .

Что происходит:

  1. BuildKit монтирует файл ~/.pypi-auth в /run/secrets/pypi_auth внутри build’а
  2. RUN читает оттуда секрет, использует для pip install
  3. После RUN секрет не попадает ни в слой, ни в image history

Альтернативно через env (BuildKit читает из env-переменной):

PYPI_AUTH=$(cat ~/.pypi-auth) docker build \
    --secret id=pypi_auth,env=PYPI_AUTH \
    -t app:v1 .

В GitHub Actions:

- name: Build
  uses: docker/build-push-action@v5
  with:
    context: .
    push: true
    secrets: |
      pypi_auth=${{ secrets.PYPI_AUTH }}

RUN —mount=type=ssh: SSH для git clone

Для приватных git-репо во время сборки:

# syntax=docker/dockerfile:1.7
RUN --mount=type=ssh \
    git clone [email protected]:org/private-repo.git /tmp/repo
ssh-add ~/.ssh/id_ed25519   # добавить ключ в agent
docker build --ssh default -t app:v1 .

BuildKit пробрасывает SSH-agent в build, git может авторизоваться через него. Ключ в образ не попадает.


docker buildx и multi-platform builds

docker buildx это надстройка над BuildKit, которая добавляет:

  • Multi-platform builds (--platform linux/amd64,linux/arm64)
  • Remote builders (для shared CI cache)
  • buildx bake (HCL-конфиг для группированных builds)
# Создать builder с поддержкой multi-platform (через QEMU emulation)
docker buildx create --use --name multi

# Build для двух платформ
docker buildx build \
    --platform linux/amd64,linux/arm64 \
    -t ghcr.io/org/app:v1 \
    --push \
    .

Результат — multi-arch manifest list в registry. При pull на amd64 — amd64 вариант, на arm64 — arm64. Это критично для команд с MacBook M-серии (arm64) и Linux x86 production (amd64).


Remote cache: ускорение CI

В CI каждый runner начинает с пустым local cache. --cache-to / --cache-from решают это, сохраняя cache в registry:

# Push cache в registry после build
docker buildx build \
    --cache-to type=registry,ref=ghcr.io/org/app:cache,mode=max \
    --cache-from type=registry,ref=ghcr.io/org/app:cache \
    -t ghcr.io/org/app:v1 \
    --push \
    .

Cache хранится как отдельный image-tag (app:cache). На следующем build CI pull’ит cache из registry, и build идёт «инкрементально». Это уменьшает CI-build с 5 минут до 30 секунд для типичного DE-сервиса.

Альтернативные cache backends:

  • type=gha — GitHub Actions cache
  • type=local — локальная директория (mount в CI-volume)
  • type=s3 — AWS S3

Попробуй сам

Сравни с и без cache mount:

mkdir buildkit-cache && cd buildkit-cache

cat > requirements.txt <<'EOF'
pandas==2.2.3
numpy==2.1.3
psycopg2-binary==2.9.10
requests==2.32.3
EOF

# Без cache mount
cat > Dockerfile.no-cache <<'EOF'
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
EOF

# С cache mount
cat > Dockerfile.cache <<'EOF'
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install -r requirements.txt
EOF

# Первый build без cache
docker build --no-cache -f Dockerfile.no-cache -t test:no-cache .
# ~30s, качает с PyPI

# Изменим requirements -- добавим новую зависимость
echo "rich==13.9.4" >> requirements.txt

# Второй build без cache mount -- pip качает заново ВСЁ
time docker build -f Dockerfile.no-cache -t test:no-cache .
# 30s

# Build с cache mount (первый раз -- кэш пуст)
docker build --no-cache -f Dockerfile.cache -t test:cache .

# Снова добавим зависимость
echo "click==8.1.7" >> requirements.txt

# Build с cache mount -- pip использует cache, качает только новую
time docker build -f Dockerfile.cache -t test:cache .
# ~3s -- огромная разница

cd .. && rm -rf buildkit-cache
docker rmi test:no-cache test:cache

Проверка знанийKnowledge check
В CI Dockerfile использует RUN --mount=type=cache,target=/root/.cache/pip pip install. Каждый build на свежем CI-runner'е занимает 60+ секунд на pip install. Cache mount не помогает. Почему и как исправить?
ОтветAnswer
Cache mount хранится локально на runner'е (в /var/lib/docker/buildkit/). На свежем CI-runner'е этот cache пуст -- каждый раз pip качает с PyPI. RUN --mount=type=cache работает только если runner persistent (self-hosted, тот же runner используется повторно). Решение для эфемерных runners: использовать remote cache через --cache-to type=registry,ref=ghcr.io/org/app:cache,mode=max --cache-from type=registry,ref=ghcr.io/org/app:cache. BuildKit пушит cache как отдельный image-tag в registry, следующий runner pull'ит его перед build. Это превращает 60s в 5-10s. Альтернативы: type=gha для GitHub Actions cache (бесплатно до 10GB), type=s3.

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

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что делает RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt?

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

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

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

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