Docker layer caching: ускоряем сборку образов в CI/CD pipeline
Сборка 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-минутные билды.