Top.Mail.Ru
OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

BuildKit cache mounts: ускоряем Composer/NPM сборки и экономим I/O

Cache mounts в Docker BuildKit — простой способ ускорить Composer и NPM/Yarn/PNPM сборки, уменьшить трафик и износ дисков. В статье — практические Dockerfile-примеры без раздутия образов, параметры mount, грабли с правами и инвалидацией кэша, мини-бенчмарк и рекомендации по CI/CD.
BuildKit cache mounts: ускоряем Composer/NPM сборки и экономим I/O

Если вы собираете PHP/Node.js образы каждый день, то львиная доля времени уходит на загрузку артефактов и запись их во временные каталоги менеджеров пакетов. Docker BuildKit с cache mounts решает именно эту проблему: повторно использует кэш директорий Composer/NPM/Yarn/PNPM между сборками, резко снижает I/O и ускоряет конвейер.

Что такое cache mounts в BuildKit

BuildKit добавил в Dockerfile директивы RUN --mount=..., позволяющие монтировать в шаг сборки разные источники: кэш, секреты, бинды. Тип type=cache создаёт персистентное хранилище на стороне билдера и подключает его в контейнер сборки в указанную папку. Данные кэша переживают шаги и сборки, пока вы их явно не очистите.

В отличие от слоёв образа, cache mount:

  • не попадает в итоговый образ (нет риска раздуть размер артефактами);
  • не зависит от порядка команд в Dockerfile, кэшируется отдельно от layer cache;
  • может быть одним и тем же хранилищем для множества проектов (через общий id=...).

Слои Docker отвечают за неизменяемые снапшоты ФС. Cache mounts — это локальные, персистентные для билдера директории, которые BuildKit монтирует на время шага RUN.

Общие параметры cache mount

Чаще всего используются такие параметры:

  • target — путь в контейнере сборки, куда монтируется кэш.
  • id — имя кэша. Одинаковый id позволяет переиспользовать один кэш между разными проектами/контекстами.
  • sharing — стратегия совместного доступа: shared (по умолчанию), locked (рекомендуется для менеджеров пакетов), private.
  • uid/gid/mode — права на директорию кэша внутри контейнера сборки (полезно при пользователе без root).
  • ro — монтировать только для чтения (редко для кэша пакетов, т.к. он должен пополняться).

Подготовка: включаем BuildKit и синтаксис Dockerfile

В современных версиях Docker BuildKit включён по умолчанию. Если нужно задать явно, используйте переменную окружения при сборке:

DOCKER_BUILDKIT=1 docker build -t app:dev .

И добавьте в Dockerfile синтаксис:

# syntax=docker/dockerfile:1.4

Эта строка включает поддержку RUN --mount и других расширений.

Диаграмма потока кэша BuildKit для менеджеров пакетов

Composer: правильные каталоги кэша и пример Dockerfile

Composer хранит кэш в $COMPOSER_CACHE_DIR. По умолчанию это внутри $COMPOSER_HOME/cache. Чтобы поведение было предсказуемым, явно зададим переменные и замонтируем cache mount именно туда.

# syntax=docker/dockerfile:1.4
FROM php:8.3-cli-bookworm AS builder
ENV COMPOSER_HOME=/tmp/composer
ENV COMPOSER_CACHE_DIR=/tmp/composer/cache
WORKDIR /app
COPY composer.json composer.lock ./
RUN --mount=type=cache,target=/tmp/composer/cache,id=composer-cache,sharing=locked composer install --no-dev --prefer-dist --no-interaction --no-progress --no-scripts
COPY . .
RUN --mount=type=cache,target=/tmp/composer/cache,id=composer-cache,sharing=locked composer dump-autoload --no-dev --classmap-authoritative

Ключевые моменты:

  • Сначала копируем composer.json и composer.lock; это даёт возможность повторно использовать кэш и слои, пока lock не меняется.
  • sharing=locked снижает риск гонок, если параллельно идут несколько сборок на одном билдере.
  • Кэш не попадёт в итоговый образ и не увеличит его размер.

Non-root пользователь и права

Если сборка идёт от не-привилегированного пользователя, задайте uid/gid, чтобы Composer мог писать кэш:

RUN --mount=type=cache,target=/tmp/composer/cache,id=composer-cache,uid=1000,gid=1000,mode=0775,sharing=locked composer install --no-dev --prefer-dist --no-interaction --no-progress

Секреты для приватных репозиториев

Часто нужен auth.json или токен для приватных пакетов. Используйте секреты BuildKit — так токен не попадёт в слои:

# Предположим, секрет доступен как id=composer_auth
RUN --mount=type=secret,id=composer_auth,target=/tmp/composer/auth.json,required=true --mount=type=cache,target=/tmp/composer/cache,id=composer-cache,sharing=locked COMPOSER_AUTH=/tmp/composer/auth.json composer install --no-interaction

NPM/Yarn/PNPM: где кэш и как монтировать

Для Node.js менеджеров пакетов важно знать путь к кэшу и зафиксировать его явно, чтобы mount попадал точно в нужную директорию.

  • NPM: кэш по умолчанию в домашней директории пользователя. Рекомендуется задать NPM_CONFIG_CACHE, например /root/.npm.
  • Yarn v1: можно указать YARN_CACHE_FOLDER, например /usr/local/share/.cache/yarn или /root/.cache/yarn.
  • PNPM: хранилище в ~/.pnpm-store; задайте PNPM_STORE_DIR или --store-dir.

Пример с NPM

# syntax=docker/dockerfile:1.4
FROM node:22-bookworm AS node-builder
WORKDIR /app
ENV NPM_CONFIG_CACHE=/root/.npm
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm,id=npm-cache,sharing=locked npm ci --ignore-scripts --no-audit --no-fund
COPY . .
RUN --mount=type=cache,target=/root/.npm,id=npm-cache,sharing=locked npm run build

Пример с Yarn

# syntax=docker/dockerfile:1.4
FROM node:22-bookworm AS yarn-builder
WORKDIR /app
ENV YARN_CACHE_FOLDER=/root/.cache/yarn
COPY package.json yarn.lock ./
RUN --mount=type=cache,target=/root/.cache/yarn,id=yarn-cache,sharing=locked yarn install --frozen-lockfile --ignore-scripts --non-interactive
COPY . .
RUN --mount=type=cache,target=/root/.cache/yarn,id=yarn-cache,sharing=locked yarn build

Пример с PNPM

# syntax=docker/dockerfile:1.4
FROM node:22-bookworm AS pnpm-builder
WORKDIR /app
ENV PNPM_HOME=/root/.local/share/pnpm
ENV PNPM_STORE_DIR=/root/.pnpm-store
COPY package.json pnpm-lock.yaml ./
RUN corepack enable
RUN --mount=type=cache,target=/root/.pnpm-store,id=pnpm-store,sharing=locked pnpm install --frozen-lockfile --prefer-offline
COPY . .
RUN --mount=type=cache,target=/root/.pnpm-store,id=pnpm-store,sharing=locked pnpm build

Сборка PHP+Node в одном Dockerfile: multi-stage

Частый кейс: собрать PHP-зависимости и фронтенд ассеты, а затем собрать минимальный runtime-образ без dev-инструментов.

# syntax=docker/dockerfile:1.4
FROM php:8.3-cli-bookworm AS php-builder
WORKDIR /app
ENV COMPOSER_HOME=/tmp/composer
ENV COMPOSER_CACHE_DIR=/tmp/composer/cache
COPY composer.json composer.lock ./
RUN --mount=type=cache,target=/tmp/composer/cache,id=composer-cache,sharing=locked composer install --no-dev --prefer-dist --no-interaction --no-progress
COPY . .

FROM node:22-bookworm AS assets-builder
WORKDIR /app
ENV NPM_CONFIG_CACHE=/root/.npm
COPY --from=php-builder /app /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm,id=npm-cache,sharing=locked npm ci --ignore-scripts --no-audit --no-fund
RUN --mount=type=cache,target=/root/.npm,id=npm-cache,sharing=locked npm run build

FROM php:8.3-fpm-bookworm AS runtime
WORKDIR /var/www/app
COPY --from=php-builder /app /var/www/app
COPY --from=assets-builder /app/public/build /var/www/app/public/build
CMD ["php-fpm"]

В результате в финальном образе нет ни Composer, ни NPM, ни их кэшей. Кэш остаётся на стороне билдера и ускоряет последующие сборки.

А как насчёт apt/pip и других менеджеров?

Cache mounts подходят и для системных менеджеров пакетов, если нужно уменьшить сетевой шум. Пример для APT:

RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache,sharing=locked --mount=type=cache,target=/var/lib/apt,id=apt-lib,sharing=locked apt-get update && apt-get install -y git unzip

Но помните: индексы репозиториев быстро устаревают. Если кэш стал слишком «липким», периодически очищайте его через docker builder prune или меняйте id.

Производительность: чего ожидать

Практические наблюдения на типичных проектах:

  • Первый билд — без выигрыша (кэш пустой), но уже второй и далее: Composer/NPM скачивают лишь дельты и читают много из локального кэша.
  • Ускорение шагов установки зависимостей в 2–10 раз в зависимости от числа пакетов и пропускной способности сети.
  • Снижение пикового I/O и сетевой активности, особенно важно на средах с ограниченными IOPS.

Самый большой эффект cache mounts дают на CI-агентах и сборочных VDS, где десятки билдов проходят ежедневно: кэш «прогревается» и становится общим для проектов. Если собираете много образов, имеет смысл вынести builder на собственный VDS.

Архитектура CI-билдера с персистентным BuildKit-кэшем на сервере

Стратегии инвалидации и стабильность сборок

Чтобы сборка была воспроизводимой и кэш не ломал детерминизм:

  • Для Composer используйте composer.lock; для NPM — package-lock.json, Yarn — yarn.lock, PNPM — pnpm-lock.yaml.
  • Копируйте в образ только lock-файлы перед установкой зависимостей. Остальной код копируйте позже.
  • Фиксируйте версии инструментов (Composer, npm, yarn, pnpm), чтобы формат кэша не «гулял» между версиями.

CI/CD: GitHub Actions, GitLab, TeamCity и др.

Cache mounts живут внутри билдера. Это означает:

  • Если каждый раз создаётся новый ephemeral builder, кэш не сохранится. Настройте persistent builder на агенте.
  • Для нескольких агентов кэш не реплицируется сам по себе. Пингуйте сборки к одному билдеру или используйте внешний layer cache (отдельная тема).
  • Очищайте периодически кэш, чтобы избежать «вспухания» диска: используйте docker builder prune и смотрите объёмы через docker system df.

Сравнение: cache mounts vs слойный кэш

Чем cache mounts отличаются от классического layer caching в Docker:

  • Layer cache зависит от точности совпадения инструкций и контента. Любое изменение lock-файла — и слой пересобирается.
  • Cache mounts не привязаны к слоям образа и «живут» в среде билдера. Даже если слой пересобирается, кэш пакетов остаётся тёплым.
  • Cache mounts не увеличивают итоговый образ, в отличие от копирования кэшей внутрь.

Диагностика: как понять, что кэш работает

Признаки работающего cache mount:

  • Менеджер пакетов сообщает о «from cache» или скачивает существенно меньше артефактов.
  • Во время шага RUN в директории target появляются файлы; при последующих билдах их видно сразу.
  • Общий размер пространства билдера растёт после нескольких сборок — кэш заполняется.
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Частые вопросы и грабли

Нужно ли уникализировать id для каждого проекта?

Не обязательно. Общий id для Composer или NPM позволит переиспользовать скачанные пакеты между проектами. Исключение — если вы опасаетесь конфликтов из-за нестабильных кэшей; тогда используйте префикс id с именем проекта.

Почему после очистки билдера сборка снова долгая?

Cache mounts локальны для билдера. После очистки (builder prune) кэш пуст, и нужно одно-два прохождения, чтобы прогреть его заново.

Можно ли сделать кэш «только для чтения»?

Технически да: ro. Практически для менеджеров пакетов это бесполезно — они должны записывать новые версии и индексы.

Как быть с monorepo и несколькими lock-файлами?

Монтируйте один общий кэш на менеджер пакетов и запускайте установку отдельно в каждом пакете. Это сохранит переиспользование артефактов на уровне менеджера.

Мини-бенчмарк

На проекте с ~80 PHP-пакетами и ~120 NPM-пакетами:

  • Первый билд: Composer 40–60 секунд, NPM 90–120 секунд.
  • Дальнейшие билды (без изменения lock): Composer 6–15 секунд, NPM 15–30 секунд.

Экономия времени в 4–8 раз и значительное снижение сетевого трафика. На дисках с ограниченным IOPS эффект ещё заметнее.

Рекомендованные практики

  • Всегда явно задавайте директории кэша менеджера пакетов и монтируйте их как type=cache с sharing=locked.
  • Копируйте lock-файлы до установки зависимостей — это стабилизирует слои.
  • Фиксируйте версии инструментов и node/php-образов в пределах минорных обновлений.
  • Периодически чистите кэш билдера и следите за диском, особенно в CI.
  • Используйте multi-stage, чтобы кэш не попадал в runtime-образ.

Итоги

Cache mounts в BuildKit — один из самых простых и недооценённых способов ускорить Docker-сборки с Composer и NPM/Yarn/PNPM. Пара строк в Dockerfile избавляют от повторной загрузки тысяч артефактов, уменьшают I/O, экономят трафик и время разработчиков. Настройте постоянный builder, зафиксируйте пути к кэшу, используйте sharing=locked, и ваши сборочные конвейеры станут ощутимо быстрее и стабильнее.

Поделиться статьей

Вам будет интересно

TLS session tickets в Nginx: ключи, ротация и единые тикеты на кластер OpenAI Статья написана AI (GPT 5)

TLS session tickets в Nginx: ключи, ротация и единые тикеты на кластер

Если у вас несколько Nginx-узлов за балансировщиком, резюмирование TLS без лишних накладных расходов удобно реализовать через sess ...
rclone crypt: клиентское шифрование бэкапов в S3 и ротация ключей OpenAI Статья написана AI (GPT 5)

rclone crypt: клиентское шифрование бэкапов в S3 и ротация ключей

Если данные улетают в S3 без клиентского шифрования, вы зависите от провайдера и прав к бакету. Разбираем rclone crypt: как включи ...
Docker HEALTHCHECK правильно: шаблоны проверок и связка с restart‑политиками OpenAI Статья написана AI (GPT 5)

Docker HEALTHCHECK правильно: шаблоны проверок и связка с restart‑политиками

HEALTHCHECK в Docker — недооценённый инструмент стабильности. От корректной проверки зависит запуск зависимостей, обновления без д ...