Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Оптимизация образов
optimization

Оптимизация образов

Кэширование слоёв, минимизация размера, best practices

Оптимизация Docker-образов

Маленькие образы быстрее собираются, безопаснее и дешевле в хранении. Изучите техники оптимизации размера и ускорения сборки.

#Зачем оптимизировать образы?

Причины оптимизировать:

  1. Скорость — меньше размер = быстрее загрузка и развёртывание
  2. Безопасность — меньше пакетов = меньше уязвимостей
  3. Стоимость — хранение и передача образов стоят денег
  4. Кэширование — эффективное кэширование ускоряет CI/CD
  5. Производительность — меньше потребление памяти и диска

#Анализ размера образа

#docker history

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

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

#Техники оптимизации

#1. Выбор базового образа

Иерархия размеров:

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

#2. Multi-stage сборка

См. тему "Многоэтапная сборка".

# Было: 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 --from=builder /app/myapp . CMD ["./myapp"]

#3. Эффективное кэширование слоёв

Порядок инструкций важен:

# ✅ Хорошо — кэш используется при изменении кода 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 кэширует слои. Если инструкция или её контекст изменились, кэш сбрасывается для этой и всех последующих инструкций.

#4. Объединение RUN-команд

# ✅ Хорошо — один слой 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/*

Преимущества:

  • Меньше слоёв
  • Меньше размер (кэш apt удаляется в том же слое)

#5. Очистка кэша в том же слое

# 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

#6. .dockerignore

Исключите лишние файлы из контекста сборки:

# .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 . .

#7. Использование specific tags

# ✅ Хорошо — воспроизводимость FROM python:3.11.4-slim FROM node:18.16.0-alpine # ❌ Плохо — может измениться FROM python:latest FROM node:18 FROM python

#8. Минимизация количества слоёв

# ✅ Хорошо — 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.txt

#9. Оптимизация для конкретных языков

#Python

FROM 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"]

#Node.js

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"]

#Java

# Этап 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 --from=builder /app/target/*.jar app.jar # Очистка RUN rm -rf /tmp/* CMD ["java", "-jar", "app.jar"]

#10. Сжатие бинарников

#Go

RUN CGO_ENABLED=0 GOOS=linux go build \ -ldflags="-w -s" \ -o myapp

Флаги:

  • -w — удалить DWARF (отладочную информацию)
  • -s — удалить symbol table

#Rust

# Cargo.toml [profile.release] lto = true codegen-units = 1 panic = 'abort'
RUN cargo build --release

#Оптимизация Docker Compose

#Использование build args

services: api: build: context: . args: - NODE_ENV=production - BUILD_VERSION=1.0.0
ARG NODE_ENV=development ENV NODE_ENV=${NODE_ENV} RUN if [ "$NODE_ENV" = "production" ]; then \ npm ci --only=production; \ else \ npm ci; \ fi

#Multi-stage в Compose

services: api: build: context: . target: production # Сборка только до этапа production

#Best Practices

#1. Используйте .dockerignore всегда

# Проверьте размер контекста 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

#2. Сканируйте образы на уязвимости

# Docker Scout (Docker Desktop) docker scout cve myapp:latest # Trivy trivy image myapp:latest # Snyk snyk container test myapp:latest

#3. Используйте multi-arch сборку

docker buildx build \ --platform linux/amd64,linux/arm64 \ -t myapp:latest \ --push \ .

#4. Мониторьте размер в CI/CD

# 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

#5. Автоматизируйте оптимизацию

# hadolint — линтер Dockerfile hadolint Dockerfile # docker-slim — автоматическая оптимизация docker-slim build myapp:latest

#Сравнение оптимизаций

ТехникаБылоСталоУлучшение
Multi-stage (Go)800 МБ20 МБ40x
Alpine вместо Ubuntu77 МБ5 МБ15x
--no-cache-dir (pip)900 МБ120 МБ7.5x
.dockerignore500 МБ50 МБ10x
Объединение RUN150 МБ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 --from=builder /usr/local/lib/python3.11/site-packages \ /usr/local/lib/python3.11/site-packages COPY --from=builder /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


#📊 Case Studies

#Case Study 1: Оптимизация Python-микросервиса для E-commerce

Контекст: Команда разрабатывала микросервис для обработки заказов. Приложение на FastAPI с PostgreSQL.

Проблема:

  • Исходный образ: 1.2 ГБ
  • Время сборки: 12 минут
  • Время развёртывания в production: 8 минут
  • Частые таймауты в CI/CD

Анализ:

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 --from=builder /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)23211.5x

Выводы:

  • Multi-stage сборка критична для Python
  • slim-образы достаточно минимальны для большинства задач
  • Кэширование зависимостей ускоряет CI/CD

#Case Study 2: Go-сервис с 800 МБ до 20 МБ

Контекст: Сервис авторизации на Go (15 микросервисов в компании).

Проблема:

  • Каждый образ ~800 МБ из-за компилятора
  • 15 сервисов × 800 МБ = 12 ГБ на реестре
  • Долгое масштабирование в K8s

Решение:

# ❌ Было 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 --from=builder /app/auth-service /auth-service COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ USER nobody ENTRYPOINT ["/auth-service"]

Результат:

МетрикаДоПослеУлучшение
Размер800 МБ22 МБ36x
Время запуска в K8s45 сек5 сек9x
Стоимость хранения (15 сервисов)$12/мес$2/мес6x

Выводы:

  • scratch образ идеален для статических Go-бинарников
  • CGO_ENABLED=0 позволяет избежать зависимостей от glibc
  • Флаги -w -s удаляют отладочную информацию

#Case Study 3: Node.js приложение с кэшированием слоёв

Контекст: React + Express приложение для стартапа.

Проблема:

  • Сборка в CI/CD занимала 15 минут
  • Каждый коммит пересобирал все слои
  • Разработчики ждали сборку локально

Анализ:

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 --from=frontend-builder /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 --from=backend-builder /app/dist ./dist COPY --from=backend-builder /app/public ./public USER node CMD ["node", "dist/server.js"]

Результат:

МетрикаДоПослеУлучшение
Сборка в CI/CD15 мин4 мин3.75x
Локальная сборка8 мин2 мин4x
Размер образа950 МБ180 МБ5.3x

Выводы:

  • Разделение frontend/backend ускоряет сборку
  • npm ci --only=production уменьшает размер
  • Очистка npm cache экономит 50-100 МБ

#Case Study 4: Java Spring Boot с GraalVM Native Image

Контекст: Enterprise приложение на Spring Boot для банка.

Проблема:

  • Образ 650 МБ
  • Время запуска JVM 30-45 секунд
  • Потребление памяти 512 МБ минимум

Решение:

# ❌ Было 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 --from=builder /app/target/app ./app USER nonroot ENTRYPOINT ["./app"]

Результат:

МетрикаДоПослеУлучшение
Размер650 МБ85 МБ7.6x
Время запуска45 сек0.5 сек90x
Потребление памяти512 МБ64 МБ8x

Выводы:

  • GraalVM Native Image радикально ускоряет запуск
  • Distroless образы безопаснее Alpine
  • Для Java это maximum возможной оптимизации

#Troubleshooting

#Образ всё ещё большой

# Проанализируйте слои 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

Далее: Безопасность контейнеров