Docker: production best practices
Большинство Dockerfile в интернете — учебные. В продакшене они опасны. Этот урок показывает как делают зрелые команды.
10 правил продакшен-Dockerfile
1. Multi-stage build — всегда
Отделяй build-зависимости от runtime. Финальный образ — только то, что нужно для запуска.
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o app .
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
Результат: ~15 MB образ вместо 800+ MB с Go тулчейном.
2. Distroless или slim — не «полный Ubuntu»
- distroless (Google): только runtime, никакого shell. Атаковать нечего.
- alpine: 5MB, но musl libc — иногда несовместимо с glibc-приложениями
- *-slim (Debian/Ubuntu): хороший компромисс
- scratch: пустой — для статически слинкованных бинарей (Go, Rust)
3. Non-root user — обязательно
RUN groupadd -r app && useradd -r -g app -u 10001 app
USER 10001
# или в distroless
FROM gcr.io/distroless/static:nonroot
USER nonroot:nonroot
Если контейнер скомпрометирован — атакующий не root.
4. Pin версии и digest
# Плохо
FROM python:latest
# Лучше
FROM python:3.12-slim
# Идеально (для prod)
FROM python:3.12.5-slim@sha256:a8f3...
Digest — гарантия что образ не подменили в registry.
5. Кеш слоёв — порядок имеет значение
Часто меняющееся — в конец. Стабильные deps — в начало.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt . # редко меняется
RUN pip install -r requirements.txt # кешируется
COPY . . # часто меняется → пересборка только этого слоя
6. .dockerignore — must have
.git
.github
.venv
__pycache__
node_modules
.env
.env.*
*.log
.DS_Store
*.md
docs/
tests/
.terraform/
Не тащи в образ то, что не нужно. Безопасность + размер.
7. Чистка в том же RUN
RUN apt-get update && \
apt-get install -y --no-install-recommends curl ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
Если очистка в отдельном RUN — она не уменьшит слой (мусор уже в предыдущем слое).
8. HEALTHCHECK
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:8080/healthz || exit 1
В K8s заменяется liveness/readiness probes, но для docker-compose и swarm — must.
9. Сигналы и PID 1
# Плохо: shell form → SIGTERM не доходит до приложения
CMD python -m app
# Хорошо: exec form → процесс становится PID 1
CMD ["python", "-m", "app"]
# Идеально для multi-process: tini
RUN apt-get install -y tini
ENTRYPOINT ["tini", "--"]
CMD ["python", "-m", "app"]
10. BuildKit secrets — никогда в layers
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install
# secret монтируется только на время этого RUN, в образ не попадает
docker build --secret id=npmrc,src=$HOME/.npmrc .
Эталонный Dockerfile для Python
# syntax=docker/dockerfile:1.7
ARG PYTHON_VERSION=3.12.5
FROM python:${PYTHON_VERSION}-slim AS builder
ENV PIP_NO_CACHE_DIR=1 PYTHONDONTWRITEBYTECODE=1
WORKDIR /app
RUN pip install --no-cache-dir uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
FROM python:${PYTHON_VERSION}-slim AS runtime
# Безопасность
RUN groupadd -r app -g 10001 && useradd -r -g app -u 10001 -m -d /home/app app
WORKDIR /app
# Только venv и код, никаких build-tools
COPY --from=builder --chown=app:app /app/.venv /app/.venv
COPY --chown=app:app src/ ./src/
USER app
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
HEALTHCHECK --interval=30s --timeout=3s CMD \
python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()" \
|| exit 1
EXPOSE 8000
ENTRYPOINT ["python", "-m", "src.main"]
Сборка и проверка
# Multi-arch build (amd64 + arm64)
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:1.0 --push .
# Анализ слоёв и размера
docker history myapp:1.0
dive myapp:1.0 # интерактивный инструмент
# Security scan
trivy image myapp:1.0
grype myapp:1.0
# SBOM
syft myapp:1.0 -o spdx-json > sbom.json
# Linter Dockerfile
hadolint Dockerfile
# Подпись
cosign sign myapp:1.0
Anti-patterns (никогда!)
- ❌
FROM ubuntu:latest+apt-get installвсего подряд - ❌ Запуск как root
- ❌
ADD https://...для скачивания (используйCOPYили multi-stage) - ❌ Установка curl/wget в финальный образ «на всякий случай»
- ❌
chmod -R 777 - ❌ Хранение секретов через
ENV PASSWORD=... - ❌ Один контейнер — несколько процессов через supervisord
- ❌
latestтег в продакшене
Чек-лист готовности образа к продакшену
- ☐ Multi-stage build
- ☐ Distroless или slim база
- ☐ Non-root user
- ☐ Pin версии (slim + digest)
- ☐ .dockerignore настроен
- ☐ HEALTHCHECK
- ☐ Exec form CMD/ENTRYPOINT
- ☐ Размер < 200MB (Python/Node), < 50MB (Go)
- ☐ Trivy scan — нет HIGH/CRITICAL
- ☐ SBOM сгенерирован
- ☐ Cosign signed