Оптимизация образов
Оптимизация Docker-образов¶
Образ на 2 GB с gcc, test-зависимостями и секретами — типичная ошибка. В продакшене образ должен быть маленьким, безопасным и быстрым для скачивания. Multi-stage builds — ключевой инструмент.
Multi-stage builds¶
Идея: один Dockerfile, несколько FROM. Промежуточные стадии компилируют/собирают, финальная — содержит только артефакт.
Python-приложение¶
# ──── 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-приложение (экстремальная оптимизация)¶
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.
FROM gcr.io/distroless/python3-debian12
COPY --from=builder /app /app
WORKDIR /app
CMD ["main.py"]
Оптимизация слоёв¶
Порядок COPY имеет значение¶
# Плохо — любое изменение кода инвалидирует кеш pip install
COPY . .
RUN pip install -r requirements.txt
# Хорошо — зависимости кешируются отдельно
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
.dockerignore¶
# .dockerignore
.git
.venv
__pycache__
*.pyc
.env
.env.*
node_modules
tests/
docs/
*.md
.mypy_cache
.pytest_cache
Минимизация слоёв¶
# Плохо — 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 пользователь¶
# Создать пользователя
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)¶
# Установка
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¶
# НИКОГДА так
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¶
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 уязвимостей
- [ ] Нет секретов в образе
Проверь себя¶
Источники¶
- Docker: Multi-stage builds — официальная документация
- Docker: Best practices — оптимизация Dockerfile
- Google Distroless — минимальные образы
- Trivy — сканер уязвимостей