Перейти к содержанию

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

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

Образ на 2 GB с gcc, test-зависимостями и секретами — типичная ошибка. В продакшене образ должен быть маленьким, безопасным и быстрым для скачивания. Multi-stage builds — ключевой инструмент.


Multi-stage builds

Идея: один Dockerfile, несколько FROM. Промежуточные стадии компилируют/собирают, финальная — содержит только артефакт.

Python-приложение

Docker
# ──── Stage 1: builder ────
FROM python:3.11-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

COPY . .

# ──── Stage 2: runtime ────
FROM python:3.11-slim

# Non-root user
RUN groupadd -r app && useradd -r -g app app

WORKDIR /app
COPY --from=builder /install /usr/local
COPY --from=builder /app .

USER app
EXPOSE 8000
CMD ["gunicorn", "main:app", "-b", "0.0.0.0:8000", "-w", "4"]

Результат: финальный образ не содержит pip, wheel, gcc, build-зависимости.

Go-приложение (экстремальная оптимизация)

Docker
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app

FROM scratch
COPY --from=builder /app /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/app"]

scratch — пустой образ, 0 байт. Финальный образ = только бинарник (~10-15 MB).


Базовые образы

Образ Размер Пакетный менеджер Когда
python:3.11 ~900 MB apt (Debian) Разработка, тесты
python:3.11-slim ~120 MB apt (Debian minimal) Продакшн Python
python:3.11-alpine ~50 MB apk (Alpine) Если нет C-расширений
gcr.io/distroless/python3 ~50 MB Нет Максимальная безопасность
ubuntu:22.04 ~77 MB apt Когда нужен полный Linux
alpine:3.19 ~7 MB apk Утилиты, Go, Rust

Alpine и Python

Alpine использует musl вместо glibc. Пакеты с C-расширениями (pandas, numpy, psycopg2) могут не работать или собираться 10+ минут. Для Python → slim надёжнее.

Distroless

Google Distroless — образы без shell, без пакетного менеджера, без лишних утилит. Если злоумышленник попал в контейнер — он не может запустить bash, curl, wget.

Docker
FROM gcr.io/distroless/python3-debian12
COPY --from=builder /app /app
WORKDIR /app
CMD ["main.py"]

Оптимизация слоёв

Порядок COPY имеет значение

Docker
# Плохо — любое изменение кода инвалидирует кеш pip install
COPY . .
RUN pip install -r requirements.txt

# Хорошо — зависимости кешируются отдельно
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

.dockerignore

Text Only
# .dockerignore
.git
.venv
__pycache__
*.pyc
.env
.env.*
node_modules
tests/
docs/
*.md
.mypy_cache
.pytest_cache

Минимизация слоёв

Docker
# Плохо — 3 слоя
RUN apt-get update
RUN apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/*

# Хорошо — 1 слой
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

Безопасность

Non-root пользователь

Docker
# Создать пользователя
RUN groupadd -r app && useradd -r -g app -d /app -s /sbin/nologin app

# Сменить владельца
COPY --chown=app:app . /app

# Запуск от non-root
USER app

Контейнер от root = root на хосте

Если контейнер запущен от root и злоумышленник вырвется из контейнера (container escape), он получит root на хосте. USER app — обязательно в продакшене.

Сканирование уязвимостей (Trivy)

Bash
# Установка
brew install trivy  # или apt install trivy

# Сканирование образа
trivy image myapp:latest

# Только критичные
trivy image --severity CRITICAL,HIGH myapp:latest

# В CI (GitHub Actions)
- name: Trivy scan
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: myapp:latest
    severity: CRITICAL,HIGH
    exit-code: 1

Секреты в Docker

Docker
# НИКОГДА так
ENV DATABASE_PASSWORD=secret123
COPY .env /app/.env

# Правильно — через runtime
# docker run -e DATABASE_PASSWORD=secret123 myapp
# или --env-file
# docker run --env-file .env myapp

# Для build-time секретов (Docker BuildKit)
RUN --mount=type=secret,id=pip_conf,target=/etc/pip.conf \
    pip install -r requirements.txt

Healthcheck

Docker
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

Чеклист продакшн-образа

  • [ ] Multi-stage build (builder → runtime)
  • [ ] slim или distroless базовый образ
  • [ ] Non-root USER
  • [ ] .dockerignore (нет .git, .env, tests)
  • [ ] --no-cache-dir для pip
  • [ ] --no-install-recommends для apt
  • [ ] Зависимости отдельно от кода (layer caching)
  • [ ] HEALTHCHECK
  • [ ] Trivy-скан без CRITICAL уязвимостей
  • [ ] Нет секретов в образе

Проверь себя


Источники