Docker slim images: Alpine vs Distroless vs scratch — что выбрать
Выбор базового образа — одно из первых решений при контейнеризации. От него зависит размер итогового image, количество CVE в сканере, время cold start и возможность дебага в проде. Три главных кандидата на минимальный образ — Alpine, Distroless и scratch. Разберём каждый.
Зачем минимальные образы
Стандартный node:18 или python:3.12 на Debian весит 800-1000 MB. Внутри — сотни пакетов, которые вашему приложению не нужны, но каждый из них может содержать уязвимость. Минимальный образ даёт:
- Безопасность — меньше пакетов = меньше CVE. Trivy на debian-based находит 200+ уязвимостей, на Alpine — 5-10, на distroless — 0-3
- Размер — быстрее pull из registry, дешевле хранение, быстрее деплой
- Cold start — критично для serverless и scale-to-zero: разница между 1 GB и 50 MB образом — это секунды на старте
Alpine: компактный Linux
Alpine Linux — минималистичный дистрибутив на musl libc и BusyBox. Базовый образ alpine:3.19 весит ~7 MB.
Плюсы:
- Полноценный shell, пакетный менеджер apk
- Можно дебажить: зайти в контейнер, поставить curl, strace
- Официальные -alpine варианты для большинства рантаймов
Подводные камни:
- musl vs glibc — некоторые библиотеки (особенно с native-расширениями) собраны под glibc и падают на musl. Python-пакеты с C-extensions, Node.js native addons — зона риска
- DNS — musl резолвит DNS иначе: не поддерживает
searchиndotsиз resolv.conf так же, как glibc. В Kubernetes это может вызвать проблемы с service discovery - Python wheels — многие PyPI-пакеты не имеют musl-совместимых wheels, pip собирает из исходников (долго, нужны build-deps)
FROM node:18-alpine
WORKDIR /app
RUN apk add --no-cache dumb-init
COPY --from=build /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/main.js"]
Distroless от Google
Образы gcr.io/distroless/* содержат только рантайм языка и минимальные системные библиотеки. Нет shell, нет пакетного менеджера, нет даже ls.
Доступные варианты:
gcr.io/distroless/static-debian12— для статических бинарников (Go, Rust)gcr.io/distroless/base-debian12— glibc + libssl + ca-certificatesgcr.io/distroless/nodejs18-debian12— Node.js 18 рантаймgcr.io/distroless/python3-debian12— Python 3 рантайм
Плюсы: минимальная CVE-поверхность, glibc (нет проблем Alpine), Google поддерживает. Минусы: невозможно зайти в контейнер для дебага (нет shell), сложнее troubleshooting.
FROM gcr.io/distroless/nodejs18-debian12
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=deps /app/node_modules ./node_modules
CMD ["dist/main.js"]
Для дебага есть :debug теги с busybox shell: gcr.io/distroless/nodejs18-debian12:debug.
scratch: пустой образ
scratch — это буквально ничего. Ноль слоёв, ноль файлов. Подходит только для статически скомпилированных бинарников:
FROM golang:1.22-alpine AS build
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o server .
FROM scratch
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=build /app/server /server
ENTRYPOINT ["/server"]
Не забудьте скопировать CA-сертификаты, если приложение делает HTTPS-запросы. Также нет /tmp, нет /etc/passwd — если нужен non-root user, создайте его в build-стадии и скопируйте /etc/passwd.
Сравнительная таблица
Образ Размер CVE* Shell Debug glibc
─────────────────────────────────────────────────────────────
debian:12-slim ~80 MB 50+ да да да
alpine:3.19 ~7 MB 5-10 да да нет (musl)
distroless/base ~20 MB 0-3 нет нет** да
distroless/static ~2 MB 0 нет нет нет
scratch 0 MB 0 нет нет нет
* Типичное количество CVE при сканировании Trivy. ** Есть :debug-теги.
Один сервис — три базы
Go HTTP-сервер на каждом из вариантов:
$ docker images myserver
REPOSITORY TAG SIZE
myserver alpine 12 MB
myserver distroless 8.2 MB
myserver scratch 6.1 MB
Для Go разница между distroless и scratch минимальна — выбирайте scratch, если не нужны CA-сертификаты из системы (или копируйте их вручную). Для Node.js и Python scratch не подходит — нужен рантайм, поэтому выбор между Alpine и Distroless.
Что выбрать
- Go / Rust (static binary) → scratch или distroless/static
- Node.js / Python в проде → distroless (если не нужен shell) или Alpine (если нужен дебаг)
- Нужен shell и apk для troubleshooting → Alpine
- Максимальная безопасность, compliance → Distroless
Универсального ответа нет — выбирайте под свой стек и требования к безопасности.