Кэширование слоёв, минимизация размера, best practices
Маленькие образы быстрее собираются, безопаснее и дешевле в хранении. Изучите техники оптимизации размера и ускорения сборки.
Причины оптимизировать:
docker history nginx:latest
# Вывод:
# IMAGE CREATED CREATED BY SIZE COMMENT
# a63764d7563c 2 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon… 0B
# <missing> 2 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGQUIT 0B
# <missing> 2 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
# <missing> 2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr… 0B
# <missing> 2 weeks ago /bin/sh -c apt-get update && apt-get install… 67.3MB
# <missing> 2 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do… 0BПоказывает размер каждого слоя.
docker inspect nginx:latest --format='{{.Size}}'
# 142076000 (142 МБ)
docker inspect nginx:latest --format='{{json .RootFS.Layers}}'# dive — интерактивный анализ слоёв
brew install dive
dive nginx:latest
# docker-slim
docker-slim analyze nginx:latestИерархия размеров:
ubuntu:22.04 → ~77 МБ
debian:bullseye → ~120 МБ
python:3.11 → ~900 МБ
python:3.11-slim → ~120 МБ
python:3.11-alpine → ~50 МБ
scratch → 0 Б (только ваш бинарник)
Рекомендации:
# ✅ Хорошо — минимальный образ
FROM python:3.11-slim
FROM node:18-alpine
FROM golang:1.21-alpine
# ✅ Отлично — для статических бинарников
FROM scratch
FROM gcr.io/distroless/static-debian11
# ❌ Плохо — избыточно
FROM ubuntu:22.04
FROM debian:bullseyeСм. тему "Многоэтапная сборка".
# Было: 800 МБ
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o myapp
CMD ["./myapp"]
# Стало: 20 МБ
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM alpine:3.18
COPY /app/myapp .
CMD ["./myapp"]Порядок инструкций важен:
# ✅ Хорошо — кэш используется при изменении кода
FROM python:3.11-slim
WORKDIR /app
# Зависимости меняются редко
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Код меняется часто
COPY . .
CMD ["python", "app.py"]
# ❌ Плохо — кэш сбрасывается при любом изменении
FROM python:3.11-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "app.py"]Почему: Docker кэширует слои. Если инструкция или её контекст изменились, кэш сбрасывается для этой и всех последующих инструкций.
# ✅ Хорошо — один слой
RUN apt update && \
apt install -y git curl vim && \
rm -rf /var/lib/apt/lists/*
# ❌ Плохо — три слоя + кэш apt
RUN apt update
RUN apt install -y git curl vim
RUN rm -rf /var/lib/apt/lists/*Преимущества:
# Python
RUN pip install --no-cache-dir -r requirements.txt
# apt
RUN apt update && apt install -y pkg && \
rm -rf /var/lib/apt/lists/*
# npm
RUN npm install && npm cache clean --force
# yarn
RUN yarn install && yarn cache clean
# apk (Alpine)
RUN apk add --no-cache git curlИсключите лишние файлы из контекста сборки:
# .dockerignore
.git
.gitignore
*.md
.env
.env.local
__pycache__
*.pyc
*.pyo
*.pyd
node_modules
venv
.venv
*.log
.Dockerfile
docker-compose*.yml
.pytest_cache
.coverage
*.egg-info
dist
build
Почему важно:
COPY . .# ✅ Хорошо — воспроизводимость
FROM python:3.11.4-slim
FROM node:18.16.0-alpine
# ❌ Плохо — может измениться
FROM python:latest
FROM node:18
FROM python# ✅ Хорошо — 2 слоя COPY
COPY requirements.txt setup.py ./
RUN pip install -r requirements.txt
# ❌ Плохо — 4 слоя COPY
COPY requirements.txt .
COPY setup.py .
COPY . .
RUN pip install -r requirements.txtFROM python:3.11-slim
# Установка системных зависимостей одним слоем
RUN apt update && apt install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Кэширование зависимостей
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Удаление кэша pip (если не использовали --no-cache-dir)
RUN rm -rf /root/.cache/pip
USER appuser
CMD ["gunicorn", "app:app"]FROM node:18-alpine
WORKDIR /app
# Кэширование зависимостей
COPY package*.json ./
RUN npm ci --only=production
# Копирование только необходимых файлов
COPY dist ./dist
# Очистка npm кэша
RUN npm cache clean --force
USER node
CMD ["node", "dist/index.js"]# Этап 1: Сборка
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
# Кэширование зависимостей
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# Этап 2: Запуск с JRE
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
# Копирование JAR
COPY /app/target/*.jar app.jar
# Очистка
RUN rm -rf /tmp/*
CMD ["java", "-jar", "app.jar"]RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-w -s" \
-o myappФлаги:
-w — удалить DWARF (отладочную информацию)-s — удалить symbol table# Cargo.toml
[profile.release]
lto = true
codegen-units = 1
panic = 'abort'RUN cargo build --releaseservices:
api:
build:
context: .
args:
- NODE_ENV=production
- BUILD_VERSION=1.0.0ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}
RUN if [ "$NODE_ENV" = "production" ]; then \
npm ci --only=production; \
else \
npm ci; \
fiservices:
api:
build:
context: .
target: production # Сборка только до этапа production# Проверьте размер контекста
docker build -t myapp . 2>&1 | grep "Sending build context"
# Sending build context to Docker daemon 2.048kB
# Без .dockerignore может быть:
# Sending build context to Docker daemon 500MB# Docker Scout (Docker Desktop)
docker scout cve myapp:latest
# Trivy
trivy image myapp:latest
# Snyk
snyk container test myapp:latestdocker buildx build \
--platform linux/amd64,linux/arm64 \
-t myapp:latest \
--push \
.# GitHub Actions
- name: Check image size
run: |
SIZE=$(docker inspect myapp --format='{{.Size}}')
if [ $SIZE -gt 100000000 ]; then
echo "Image size exceeds 100MB"
exit 1
fi# hadolint — линтер Dockerfile
hadolint Dockerfile
# docker-slim — автоматическая оптимизация
docker-slim build myapp:latest| Техника | Было | Стало | Улучшение |
|---|---|---|---|
| Multi-stage (Go) | 800 МБ | 20 МБ | 40x |
| Alpine вместо Ubuntu | 77 МБ | 5 МБ | 15x |
| --no-cache-dir (pip) | 900 МБ | 120 МБ | 7.5x |
| .dockerignore | 500 МБ | 50 МБ | 10x |
| Объединение RUN | 150 МБ | 120 МБ | 1.25x |
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 8000
CMD ["python", "app.py"]Размер: ~950 МБ
FROM python:3.11-slim AS builder
WORKDIR /app
# Системные зависимости
RUN apt update && apt install -y --no-install-recommends \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# Python зависимости
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Этап 2: Запуск
FROM python:3.11-slim
# Копирование зависимостей
COPY /usr/local/lib/python3.11/site-packages \
/usr/local/lib/python3.11/site-packages
COPY /usr/local/bin/gunicorn \
/usr/local/bin/gunicorn
WORKDIR /app
# Только код приложения
COPY app.py .
# Пользователь
RUN useradd --create-home --shell /bin/bash appuser
USER appuser
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]Размер: ~150 МБ
Улучшение: 6.3x
Контекст: Команда разрабатывала микросервис для обработки заказов. Приложение на FastAPI с PostgreSQL.
Проблема:
Анализ:
docker history myapp:latest | head -20
# Показало, что 800 МБ занимают build-зависимостиРешение:
# ❌ Было
FROM python:3.9
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
# ✅ Стало — multi-stage
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.11-slim
COPY /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
USER nobody
CMD ["python", "app.py"]Результат:
| Метрика | До | После | Улучшение |
|---|---|---|---|
| Размер | 1.2 ГБ | 180 МБ | 6.7x |
| Сборка | 12 мин | 3 мин | 4x |
| Развёртывание | 8 мин | 1 мин | 8x |
| Уязвимости (CRITICAL) | 23 | 2 | 11.5x |
Выводы:
Контекст: Сервис авторизации на Go (15 микросервисов в компании).
Проблема:
Решение:
# ❌ Было
FROM golang:1.21
WORKDIR /app
COPY . .
RUN go build -o auth-service
CMD ["./auth-service"]
# ✅ Стало — multi-stage + alpine + статическая компиляция
# Этап 1: Сборка
FROM golang:1.21-alpine AS builder
RUN apk add --no-cache git
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-w -s" \
-o auth-service .
# Этап 2: Запуск
FROM scratch
COPY /app/auth-service /auth-service
COPY /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
USER nobody
ENTRYPOINT ["/auth-service"]Результат:
| Метрика | До | После | Улучшение |
|---|---|---|---|
| Размер | 800 МБ | 22 МБ | 36x |
| Время запуска в K8s | 45 сек | 5 сек | 9x |
| Стоимость хранения (15 сервисов) | $12/мес | $2/мес | 6x |
Выводы:
scratch образ идеален для статических Go-бинарниковCGO_ENABLED=0 позволяет избежать зависимостей от glibc-w -s удаляют отладочную информациюКонтекст: React + Express приложение для стартапа.
Проблема:
Анализ:
docker build --progress=plain -t myapp . 2>&1 | grep -E "(CACHED|RUN)"
# Показало, что npm install запускается каждый раз зановоРешение:
# ❌ Было
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["node", "dist/server.js"]
# ✅ Стало — оптимизированное кэширование + multi-stage
# Этап 1: Сборка frontend
FROM node:18-alpine AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ .
RUN npm run build
# Этап 2: Сборка backend
FROM node:18-alpine AS backend-builder
WORKDIR /app
COPY backend/package*.json ./
RUN npm ci
COPY backend/ .
COPY /app/frontend/dist ./public
RUN npm run build
# Этап 3: Production
FROM node:18-alpine
WORKDIR /app
COPY backend/package*.json ./
RUN npm ci --only=production && npm cache clean --force
COPY /app/dist ./dist
COPY /app/public ./public
USER node
CMD ["node", "dist/server.js"]Результат:
| Метрика | До | После | Улучшение |
|---|---|---|---|
| Сборка в CI/CD | 15 мин | 4 мин | 3.75x |
| Локальная сборка | 8 мин | 2 мин | 4x |
| Размер образа | 950 МБ | 180 МБ | 5.3x |
Выводы:
npm ci --only=production уменьшает размерКонтекст: Enterprise приложение на Spring Boot для банка.
Проблема:
Решение:
# ❌ Было
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/*.jar app.jar
CMD ["java", "-jar", "app.jar"]
# ✅ Стало — GraalVM Native Image
# Этап 1: Сборка
FROM ghcr.io/graalvm/native-image:ol8-java17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests -Pnative native:compile
# Этап 2: Запуск
FROM gcr.io/distroless/base-debian11
WORKDIR /app
COPY /app/target/app ./app
USER nonroot
ENTRYPOINT ["./app"]Результат:
| Метрика | До | После | Улучшение |
|---|---|---|---|
| Размер | 650 МБ | 85 МБ | 7.6x |
| Время запуска | 45 сек | 0.5 сек | 90x |
| Потребление памяти | 512 МБ | 64 МБ | 8x |
Выводы:
# Проанализируйте слои
dive myapp:latest
# Проверьте, что попало в образ
docker run --rm -it myapp:latest sh
du -sh /*# Проверьте порядок инструкций
docker build --progress=plain -t myapp . 2>&1 | grep "CACHED"
# Очистите кэш и пересоберите
docker builder prune -a
docker build --no-cache -t myapp .# Используйте BuildKit
export DOCKER_BUILDKIT=1
docker build -t myapp .
# Кэшируйте зависимости
# Проверьте .dockerignoreВ следующей теме вы изучите безопасность контейнеров: изоляцию, сканирование уязвимостей и security best practices.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.