lenec ru

← все посты

Docker layer caching: ускоряем сборку образов в CI/CD pipeline

13K

Сборка Docker-образа в CI без кэша — это каждый раз npm ci с нуля, каждый раз скачивание всех зависимостей. На крупном проекте это 5-10 минут на каждый коммит. Layer caching и BuildKit cache mounts сокращают это до секунд — если правильно настроить Dockerfile и CI pipeline.

Как работает layer cache

Docker кэширует каждую инструкцию Dockerfile как отдельный слой. Если инструкция и её контекст не изменились — слой берётся из кэша. Но есть правило: если один слой инвалидирован, все последующие тоже пересобираются.

Плохой порядок:

COPY . .
RUN npm ci
RUN npm run build

Любое изменение в любом файле инвалидирует COPY . . → npm ci запускается заново. Хороший порядок:

COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

Теперь npm ci пересобирается только при изменении lock-файла. Изменения в исходниках инвалидируют только COPY . . и build.

BuildKit cache mounts

BuildKit (включён по умолчанию с Docker 23+) поддерживает монтирование кэш-директорий, которые переживают пересборку:

# syntax=docker/dockerfile:1
FROM node:18-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN npm run build

Директория /root/.npm сохраняется между сборками. Даже если слой инвалидирован, npm не скачивает пакеты заново — берёт из кэша.

Для других языков:

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

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

CI: --cache-to и --cache-from

В CI нет локального кэша между запусками. Решение — хранить кэш во внешнем хранилище. BuildKit поддерживает три бэкенда:

  • registry — кэш хранится как отдельный образ в container registry
  • gha — GitHub Actions cache (бесплатно, до 10 GB)
  • local — директория на диске (для self-hosted runners)
# Registry cache
docker buildx build \
  --cache-from type=registry,ref=ghcr.io/myorg/myapp:cache \
  --cache-to type=registry,ref=ghcr.io/myorg/myapp:cache,mode=max \
  -t ghcr.io/myorg/myapp:latest \
  --push .

# GitHub Actions cache
docker buildx build \
  --cache-from type=gha \
  --cache-to type=gha,mode=max \
  -t ghcr.io/myorg/myapp:latest \
  --push .

Параметр mode=max кэширует все слои, включая промежуточные стадии multi-stage build. По умолчанию (mode=min) кэшируются только слои финального образа.

GitHub Actions + buildx

Полный workflow с кэшированием:

name: Build and Push
on:
  push:
    branches: [main]

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

      - uses: docker/setup-buildx-action@v3

      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

GitHub Actions cache backend — самый простой вариант: не нужен отдельный registry-тег, кэш автоматически привязан к ветке.

Метрики: до и после

Реальные замеры для Node.js-проекта (Express + TypeScript + Prisma, ~800 зависимостей):

Сценарий                          Время сборки
──────────────────────────────────────────────
Без кэша (cold build)             4m 32s
Layer cache (lock не менялся)      0m 18s
Cache mount (lock изменился)       1m 05s
Registry cache в CI (warm)         0m 45s
Registry cache в CI (cold)         4m 40s

Layer cache при неизменном lock-файле даёт ускорение в 15x. Cache mount при изменении зависимостей экономит ~70% времени за счёт локального npm-кэша.

Подводные камни

  • .dockerignore — без него node_modules с хоста попадают в контекст и инвалидируют кэш
  • ARG перед FROM — инвалидирует весь кэш при изменении аргумента. Ставьте ARG после FROM, где возможно
  • mode=max vs mode=min — max кэширует больше, но занимает больше места. Для registry cache следите за размером
  • TTL — GitHub Actions cache живёт 7 дней без обращения. Registry cache — пока не удалите вручную

Правильно настроенный layer cache превращает CI-сборку из боттлнека в формальность. Начните с порядка инструкций в Dockerfile, добавьте cache mounts, подключите registry или gha cache — и забудьте про 5-минутные билды.

Комментарии 0

  • Будьте первым, кто оставит комментарий.

Войдите, чтобы оставить комментарий.