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

Docker BuildKit registry cache в CI: быстрые сборки на любом раннере

Долгие Docker‑сборки в CI — классическая боль: свежие раннеры, мало диска и кэш каждый раз с нуля. Registry‑backed cache в BuildKit решает проблему: кэш хранится в реестре и доступен из любого раннера и ветки. В статье — практика и готовые YAML для GitHub Actions.
Docker BuildKit registry cache в CI: быстрые сборки на любом раннере

Если вы используете Docker в CI/CD и видите, как каждый новый билд на свежем раннере скачивает все зависимости и пересобирает мир, значит пора включать BuildKit и вынести кэш в реестр контейнеров. Такой подход называется registry-backed cache: BuildKit умеет экспортировать и импортировать кэш в OCI-совместимый реестр, и благодаря этому кэш становится общим для всех раннеров, веток, даже для локальной разработки (если нужна).

Зачем именно registry-backed cache

BuildKit поддерживает несколько бэкендов кэша: локальный диск (type=local), встроенный в образ (type=inline), кэш поставщика CI (например, type=gha), и реестр контейнеров (type=registry). В корпоративном и командном сценарии чаще всего выигрывает именно реестр:

  • Кэш доступен всем раннерам без привязки к их диску и времени жизни VM.
  • Кэш может переживать пересоздание инфраструктуры CI.
  • Кэшом могут пользоваться разные пайплайны и репозитории (если нужно и если есть доступ к реестру).
  • Не раздуваете итоговые образы, как при type=inline, и не зависите от конкретного провайдера CI.

Для площадок с «холодными» ephemeral-раннерами это зачастую самый быстрый и предсказуемый способ резко сократить время сборки.

Как это работает

BuildKit при экспорте кэша в реестр публикует специальные артефакты (OCI-объекты), содержащие метаданные о шагах сборки и ссылки на уже существующие слои. При последующих билдах BuildKit импортирует эти метаданные и быстро находит совпадающие шаги, не пересобирая их. Это даёт «CACHED» для подавляющей части слоёв, если Dockerfile составлен корректно.

Важно: registry-cache хранит метаданные сборки и использует уже имеющиеся слои. Если ваш образ или его base-образы приватные, доступ к реестру и артефактам кэша должен быть настроен соответствующим образом.

Схема BuildKit registry cache: импорт и экспорт кэша между раннером и реестром

Быстрый старт: CLI

Ниже — минимальный пример, как собрать и сразу выгрузить кэш в реестр. Предполагаем, что Buildx уже включен и вы залогинились в свой реестр.

# 1) Включаем BuildKit и plain-лог
export DOCKER_BUILDKIT=1
export BUILDKIT_PROGRESS=plain

# 2) Создаём builder c драйвером docker-container
docker buildx create --use --name ci-builder

# 3) Логин в реестр (замените на ваш реестр и пользователя)
docker login REGISTRY_EXAMPLE

# 4) Сборка с импортом/экспортом кэша в один и тот же ref
docker buildx build --cache-from type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache --cache-to type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache,mode=max -t REGISTRY_EXAMPLE/ORG/APP:commit-SHA --push .

Первый прогон будет «холодным». Начиная со второго, при неизменном Dockerfile структура слоёв переиспользуется, а скорость сборки заметно растёт.

GitHub Actions: готовый workflow

В CI особенно удобно использовать action для Buildx. Пример для многоплатформенной сборки с кэшем в реестре:

name: docker-build

on:
  push:
    branches: [ main ]
  pull_request:

env:
  REGISTRY: REGISTRY_EXAMPLE
  IMAGE_NAME: ORG/APP
  CACHE_REF_MAIN: REGISTRY_EXAMPLE/ORG/APP:buildcache-main

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build and push with registry cache
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          platforms: linux/amd64,linux/arm64
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: |
            type=registry,ref=${{ env.CACHE_REF_MAIN }}
          cache-to: |
            type=registry,ref=${{ env.CACHE_REF_MAIN }},mode=max
          provenance: false
          sbom: false

Пояснения:

  • cache-to и cache-from указывают один и тот же ref, чтобы на каждом прогоне кэш актуализировался.
  • mode=max расширяет объём экспортируемого кэша (глубокие графы), что помогает последующим билдам.
  • Отключение provenance и sbom ускоряет сборку, если эти артефакты не нужны в CI.
FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Лучшие практики Dockerfile под BuildKit

1) Максимум детерминизма

Чем более детерминированны слои, тем стабильнее попадание в кэш. Не используйте легко меняющиеся значения в аргументах и LABEL на ранних слоях (например, текущую дату). Перенесите такие метаданные ближе к концу.

2) Правильный порядок COPY

Классическая оптимизация для Node.js/Go/PHP/Composer и т.д. — сначала копировать lock-файлы и манифесты зависимостей, устанавливать их, и лишь затем копировать остальное. Например, для Node.js:

# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci

FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:20-alpine AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
CMD ["node","dist/index.js"]

При неизменных package.json/package-lock.json шаг npm ci будет стабильно кешироваться.

3) Кэшируемые монтирования

BuildKit умеет давать временные кэши для инструментов сборки и пакетных менеджеров. Это не тот же кэш, что registry-backed, но вместе они дают максимальный эффект. См. также подробный разбор кэшируемых монтирований BuildKit.

# apt
RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt/lists apt-get update && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/*

# Go
RUN --mount=type=cache,target=/go/pkg/mod --mount=type=cache,target=/root/.cache/go-build go build -v ./...

# Composer
RUN --mount=type=cache,target=/composer/cache composer install --no-dev --prefer-dist --no-interaction

Такие кэши живут только в рамках конкретного билдера/хоста, но сильно ускоряют «горячие» шаги и уменьшают трафик.

4) Секреты — через --mount=type=secret

Чтобы не «прожечь» секреты в слоях и кэше, используйте секреты как монтирования:

# Dockerfile
RUN --mount=type=secret,id=npm_token bash -lc 'echo "//registry.npmjs.org/:_authToken=$(cat /run/secrets/npm_token)" >> ~/.npmrc && npm ci'

А в CI передавайте секрет параметром action:

with:
  secrets: |
    npm_token=${{ secrets.NPM_TOKEN }}

Секрет не попадёт в кэш и не испортит его переиспользуемость.

Варианты и нюансы конфигурации кэша

Несколько источников кэша

Вы можете указать несколько cache-from, например общий кэш main-ветки и кэш текущей ветки. BuildKit возьмёт всё, что найдёт.

cache-from: |
  type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache-main
  type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache-${{ github.ref_name }}
cache-to: |
  type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache-${{ github.ref_name }},mode=max

Так разработчики в фиче сразу получают бенефит общего кэша из main, а затем наращивают свой веточный.

Многоплатформенная сборка

Registry-cache работает и для multi-arch. Рекомендуется один общий ref, если у вас единый пайплайн:

platforms: linux/amd64,linux/arm64
cache-from: type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache
cache-to: type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache,mode=max

Если вы часто гоняете независимые билды по платформам, можно разделить на два ref, чтобы уменьшить гонки и перезаписи. Если нужен постоянный исполнитель, поднимите его на нашем облачном VDS — это даст стабильный локальный кэш и контроль над окружением раннера.

Стабильность и гонки

Одновременная запись в один и тот же ref разными джобами бывает чувствительной. Стратегии смягчения:

  • Разделять кэш по веткам, как в примере выше.
  • Использовать один общий read-only ref (только cache-from) и писать в веточный ref.
  • Включать ignore-error=true для cache-to, чтобы сбои публикации кэша не валили сборку:
cache-to: type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache,mode=max,ignore-error=true

Фрагмент workflow GitHub Actions с настройками buildx и registry‑кэша

Inline-кэш vs registry-кэш vs CI-кэш провайдера

  • type=inline: кэш вшит в сам образ. Просто, но увеличивает размер и требует пуша образа перед использованием кэша. Удобно как доп. источник (cache-from), но нежелательно как основной.
  • type=gha или аналогичный кэш провайдера: простая интеграция в пределах одной платформы CI, но кэш не разделяется с другой инфраструктурой и может иметь квоты/ограничения.
  • type=registry: кэш живёт рядом с образами, общедоступен для ваших раннеров и разработчиков (при наличии прав), не раздувает сами образы и не привязан к конкретному CI.

Безопасность

  • Не публикуйте кэш в публичный реестр, если собираете приватный код. Кэш содержит метаданные сборки.
  • Соблюдайте принцип наименьших прав для аккаунта, который пушит/читает кэш.
  • Секреты всегда передавайте через --mount=type=secret и не записывайте их в файлы, попадающие в слои.
  • Пинируйте базовые образы по тегам или digest для предсказуемости кэша. При использовании «скользящих» тегов кэш будет чаще инвалидироваться.

Поддержка и очистка registry-кэша

У BuildKit для type=registry нет штатного параметра TTL. Очистка решается политиками реестра, ручным удалением ненужных тегов кэша, ротацией ref (например, раз в неделю менять суффикс) или задачами обслуживания.

Распространённые подходы:

  • Ротация: хранить 2–3 последних ref кэша и периодически удалять старые.
  • Разделять кэш по веткам и удалять ref для удалённых веток.
  • Настроить политики в реестре: удаление старых тегов, неиспользуемых артефактов.

Диагностика и проверка попаданий в кэш

Включайте «plain» прогресс, чтобы видеть CACHED на шагах:

export BUILDKIT_PROGRESS=plain

Признаки здорового кэша: базовый образ скачивается один раз, установка зависимостей не выполняется повторно, большие слои помечаются как CACHED.

Типичные причины промахов по кэшу

  • Слишком ранний COPY всего контекста. Сначала копируйте только манифесты зависимостей и устанавливайте их, затем — остальное.
  • Непинированные зависимости. Скользящие версии ломают детерминизм.
  • Случайно меняющиеся LABEL/ARG. Не подставляйте дату/время до «тяжёлых» шагов.
  • Злоупотребление скачиванием из сети. Кэшируйте менеджеры пакетов через --mount=type=cache.
  • Частая смена базового образа. При смене базового слоя все последующие слои потребуется пересобрать.

Продвинутые сценарии

Разделение кэша по типам сборок

Иногда полезно иметь разные ref для PR и для main. PR будут быстрее за счёт общего main, но писать они будут в собственный ref, не перетирая основной кэш:

cache-from: |
  type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache-main
  type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache-pr
cache-to: type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache-pr,mode=max

Сборка без публикации образа

Иногда в PR вы не хотите пушить образ, а кэш — хотите. Это нормально: у docker/build-push-action можно оставить push: false, но при этом экспортировать кэш в реестр:

with:
  push: false
  cache-to: type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache-pr,mode=max

Сочетание локального и registry-кэша

Если у вас есть выделенные постоянные раннеры, добавьте локальный кэш как первый источник, а registry — как второй. Это даст минимальное время на «горячих» раннерах и стабильную производительность на «холодных»:

cache-from: |
  type=local,src=/var/lib/docker/buildkit-cache
  type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache
cache-to: |
  type=local,dest=/var/lib/docker/buildkit-cache
  type=registry,ref=REGISTRY_EXAMPLE/ORG/APP:buildcache,mode=max

Локальные пути подберите под вашу инфраструктуру и убедитесь, что они переживают рестарт раннера.

Практический чек-лист

  • Включен BuildKit и Buildx. Вы используете builder с драйвером docker-container.
  • Dockerfile оптимизирован: правильный порядок COPY, кэшируемые монтирования для зависимостей, секреты через --mount=type=secret.
  • Выбрана политика ref: общий для main, веточные для PR, либо единый ref для всего.
  • В CI настроен логин в реестр и права на чтение/запись кэша.
  • Есть план очистки кэша: ротация tag/ref, политики в реестре.

FAQ

Нужно ли пушить образ, чтобы заработал registry-кэш?

Нет. type=registry публикует именно кэш-артефакты. Образ можно не пушить (например, в PR), но кэш экспортировать — да.

Почему с type=inline кэш всё равно медленнее?

Потому что inline-кэш живёт внутри образа. Чтобы им воспользоваться, образ нужно уже иметь в реестре. Registry-кэш независим и доступен заранее. Плюс образы не раздуваются метаданными кэша.

Можно ли использовать один и тот же кэш для разных репозиториев?

Технически — да, если Dockerfile и контексты близки. Но это риск совпадений и загрязнения. Практичнее разделять ref по проектам и веткам, а общим делать только базовый слой или образы-«базисы», если это осознанная стратегия.

Итоги

Registry-backed cache в BuildKit — один из лучших способов ускорить Docker-сборки в CI с эфемерными раннерами: общедоступный кэш для всех джоб, никаких привязок к дискам и провайдерам CI, гибкая политика изоляции по веткам и проектам. При грамотном Dockerfile и дисциплине зависимостей время сборки сокращается в разы, а инфраструктура становится проще и предсказуемее. Начните с базового cache-from/cache-to в реестр и добавляйте продвинутые фишки по мере развития пайплайна.

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

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

Apache mod_cache: CacheQuickHandler, cache_disk vs cache_socache OpenAI Статья написана AI (GPT 5)

Apache mod_cache: CacheQuickHandler, cache_disk vs cache_socache

Если Apache — фронтенд или бэкенд для PHP/FCGI/прокси, mod_cache заметно разгружает приложение. Разбираем CacheQuickHandler, выбор ...
MySQL 8: caching_sha2_password и TLS — безопасная аутентификация без сюрпризов OpenAI Статья написана AI (GPT 5)

MySQL 8: caching_sha2_password и TLS — безопасная аутентификация без сюрпризов

В MySQL 8 по умолчанию используется caching_sha2_password. Это повысило безопасность, но ломает старые клиенты без TLS. В статье — ...
CSP на практике: nonce и hash, безопасный старт с Report-Only OpenAI Статья написана AI (GPT 5)

CSP на практике: nonce и hash, безопасный старт с Report-Only

Пошаговое внедрение Content Security Policy для защиты от XSS: чем отличаются nonce и hash, как безопасно запустить режим Report-O ...