Оптимизация сборки образов
TL;DR: BuildKit + правильный порядок COPY + multi-stage = быстрая сборка и маленький образ. Mount cache для npm/pip/go экономит минуты на каждом билде.
Скорость сборки напрямую влияет на Time-to-Market и время ожидания CI/CD пайплайнов. В этом руководстве мы рассмотрим современные техники ускорения билдов с использованием BuildKit.
1. Включение BuildKit
BuildKit — это современный движок сборки, который работает параллельно, кэширует эффективнее и поддерживает секреты.
В новых версиях Docker Desktop и Linux (23.0+) он включен по умолчанию. Если нет, включите принудительно:
export DOCKER_BUILDKIT=1
docker build .2. Кэширование слоев (Layer Caching)
Docker кэширует каждый шаг RUN, COPY. Если файл не изменился, Docker берет слой из кэша.
Стратегия: “Сначала зависимости”
Самая частая ошибка — копирование всего кода сразу.
❌ Плохо (Кэш сбрасывается при каждом изменении кода):
COPY . .
RUN npm ci✅ Хорошо (Кэш npm ci живет, пока не изменится package.json):
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# Дальше сборка приложения...3. Mount Caches (Кэш компилятора)
Даже если слой RUN npm ci инвалидировался (вы добавили одну библиотеку), BuildKit позволяет не качать все библиотеки заново, а использовать локальный кэш пакетного менеджера (pip, npm, go, maven).
Используйте синтаксис --mount=type=cache:
Node.js:
RUN --mount=type=cache,target=/root/.npm \
npm ciGo:
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o app .Apt (Debian/Ubuntu):
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y gccЭтот кэш сохраняется между разными сборками (docker build), даже если слои меняются.
4. Multi-Stage Builds (Уменьшение размера)
Не тащите компиляторы и исходный код в продакшн.
Пример 1: Go (Static Binary)
# Stage 1: Build
FROM golang:1.21-stretch AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# CGO_ENABLED=0 для статической линковки
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/app main.go
# Stage 2: Runtime (Scratch - пустой образ)
FROM scratch
# Копируем сертификаты для HTTPS запросов (в scratch их нет)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /bin/app /app
CMD ["/app"]Результат: Образ весит ~10-15MB.
Пример 2: Node.js (Frontend / React)
# Stage 1: Build Frontend
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# На выходе получаем папку /app/dist (или /build)
# Stage 2: Serve with Nginx
FROM nginx:alpine
# Копируем только статику
COPY --from=builder /app/dist /usr/share/nginx/html
# Копируем кастомный конфиг (если нужен)
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]Результат: Легкий Nginx вместо тяжелого Node.js процесса.
Пример 3: Node.js (Backend / NestJS)
Для бэкенда тоже нужен multi-stage, чтобы не тащить devDependencies (TypeScript компилятор) в прод.
# Stage 1: Build
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Production Dependencies Only
FROM node:18-alpine AS production
WORKDIR /app
COPY package*.json ./
# Ставим ТОЛЬКО prod зависимости
RUN npm ci --omit=dev
# Stage 3: Final Image
FROM node:18-alpine
WORKDIR /app
COPY --from=production /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
CMD ["node", "dist/main.js"]5. dockerignore (Контекст сборки)
Перед началом сборки Docker CLI отправляет демону все файлы из текущей папки (Build Context). Если у вас там лежит .git (200MB) или node_modules (500MB), сборка будет начинаться с долгой паузы “Sending build context to Docker daemon”.
Создайте .dockerignore:
.git
node_modules
dist
build
coverage
*.log
.env6. Multi-Platform Builds (ARM64 + AMD64)
Для сборки под Apple Silicon (M1/M2) и серверы (Intel/AMD) используйте docker buildx.
-
Создайте билдер:
docker buildx create --use -
Соберите мульти-архитектурный образ:
docker buildx build \ --platform linux/amd64,linux/arm64 \ -t my-app:latest \ --push .Флаг
--pushобязателен, так как локальный Docker не может хранить мульти-архитектурный манифест в обычном спискеdocker images. Образ улетит сразу в Registry.
Резюме: Чек-лист оптимизации
- .dockerignore настроен.
- Зависимости копируются до исходного кода.
- Используется Multi-stage (отдельно build, отдельно runtime).
- Добавлены Mount Caches (
--mount=type=cache) дляnpm/go/apt. - Используется BuildKit (вывод сборки цветной и параллельный).
Типичные ошибки
| Ошибка | Симптом | Решение |
|---|---|---|
COPY . . перед npm ci | Кэш зависимостей сбрасывается при каждом изменении кода | Сначала COPY package*.json, потом npm ci, потом COPY . . |
Нет .dockerignore | node_modules (500MB) попадает в build context | Создать .dockerignore с node_modules, .git, dist |
| Один stage для build и runtime | Образ содержит gcc, dev-зависимости (1GB+) | Multi-stage: builder + runner |
RUN apt install && RUN apt clean | apt cache остаётся в предыдущем слое | Объединять в один RUN: apt install -y pkg && rm -rf /var/lib/apt/lists/* |