Skip to main content

Що таке distroless образи і яку перевагу вони дають?

Distroless image це stripped-down container-image. Команда GoogleContainerTools Google ввела термін: «distribution-less», без повного Linux-distribution, лише голий мінімум для запуску застосунку. Результат значно менший і безпечніший за традиційні image.

Теорія

TL;DR

  • Distroless image містить: мовний runtime + твій бінар + CA-certs. Усе.
  • Без shell (без bash, без sh).
  • Без package-manager (без apt, без apk).
  • Без утиліт (без curl, без vim, без ps).
  • Підтримується Google: gcr.io/distroless/<runtime>.
  • Варіанти: static, base, cc, python3, nodejs22, java21 тощо. Плюс :debug і :nonroot flavors.
  • Чому брати: менший image (часто 5-50 MB final), drastically менша attack-surface, швидший CVE-remediation (менше для сканування).

Distroless-варіанти

ImageВмістБери для
gcr.io/distroless/staticнічого крім glibc + CA-certsStatic-бінарі (Go, Rust)
gcr.io/distroless/basestatic + glibc + busybox-staticПереважно static-бінарі з малими deps
gcr.io/distroless/ccbase + libgccC/C++ бінарі з compiler-runtime
gcr.io/distroless/python3Python-runtimePython-app
gcr.io/distroless/nodejs22Node 22-runtimeNode-app
gcr.io/distroless/java21Java 21-runtimeJVM-app

Кожен має :debug і :nonroot (краще) варіанти.

Швидкий приклад: Go-static-бінар

dockerfile
FROM golang:1.23-alpine AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/server ./cmd/server FROM gcr.io/distroless/static:nonroot COPY --from=build /out/server /server ENTRYPOINT ["/server"]

Фінальний image: ~10 MB. Лише Go-бінар + bare runtime-support файли. Жодного shell для drop-in, жодного apt-get, нічого, що не служить app напряму.

Node.js приклад

dockerfile
FROM node:22-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . RUN npm run build FROM gcr.io/distroless/nodejs22:nonroot WORKDIR /app COPY --from=build /app/dist /app/dist COPY --from=build /app/node_modules /app/node_modules CMD ["dist/server.js"]

Фінальний image: ~150 MB (Node-runtime великий). Порівняй з node:22 (~1 GB) або навіть node:22-alpine (~200 MB), і alpine має shell і apk.

Чому distroless

Безпека

  • Без shell = без shell-based експлойтів. Compromised-app не може exec bash.
  • Без package-manager = attacker не може встановити tools для escalate.
  • Менше файлів = менше CVE. Vulnerability-сканери репортять менше findings; remediation це просто «bump base-image».

Розмір

  • 80-95% менший за повний Debian/Ubuntu.
  • Менші pull, швидші cold-старти, менше storage.
  • Cumulative-impact на масштабі: сотні сервісів × мільйони pulls = реальна економія bandwidth.

Передбачуваність

  • Image містить точно те, що ти поклав. Без surprise-утиліт. Без drift між dev і прод.

Trade-off дебагу

Найбільше заперечення проти distroless: як docker exec sh?

Відповідь: ніяк. Trade-off реальний. Три обхідних шляхи:

1. :debug-варіант

bash
docker run -it --entrypoint sh gcr.io/distroless/base:debug

Tag :debug додає busybox. Бери для розробки; deploy :nonroot у прод.

2. Sidecar debug-container

У Kubernetes:

bash
kubectl debug -it pod/myapp --image=alpine --target=app

Крутить alpine-shell, що ділить namespaces з твоїм distroless-container. Можеш копати у app filesystem і процеси з sister-container.

3. Збір окремого :debug-image

dockerfile
FROM gcr.io/distroless/nodejs22:debug AS debug COPY --from=build /app /app

Дай два tag: myapp:1.0 (distroless) і myapp:1.0-debug (з shell). Deploy debug, коли треба.

Порівняння з Alpine

AlpineDistroless
Має shell?Так (busybox sh)Ні
Має package-manager?Так (apk)Ні
Розмір base~7 MB2-50 MB залежно від варіанту
Легко debug?ТакНі (потрібен :debug-варіант)
Multi-stage friendly?ТакТак
Glibc чи musl?muslglibc
Зрепортовані CVEДеякі (APK-пакети)Дуже мало

Alpine це size-champion серед full-distros; distroless б'є його на attack-surface, але ціною debuggability. Багато проектів беруть alpine як builder + distroless як final.

Типові помилки

Спроба docker exec sh на distroless-image

bash
$ docker exec -it myapp sh OCI runtime exec failed: exec failed: ... "sh": executable file not found in $PATH

Shell нема. Для debug бери :debug-варіант або sidecar-патерн.

Healthcheck, що потребує curl

dockerfile
FROM gcr.io/distroless/nodejs22:nonroot HEALTHCHECK CMD curl -f http://localhost:3000/health # Падає: немає curl у distroless

Рішення:

  • Для Node: HEALTHCHECK CMD node -e "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"
  • Для інших: включи крихітний static health-checker-бінар; або бери external-healthcheck (Kubernetes-probe замість Docker-healthcheck).

Бери gcr.io/distroless/base для Go-бінаря, що потребує glibc

dockerfile
FROM gcr.io/distroless/static AS final # ← може не працювати для CGO-enabled бінарів

Якщо твій Go-бінар використовує CGO (будь-яка C-interop), бери cc або base-варіант. Для pure-Go бери static.

Забути :nonroot

dockerfile
FROM gcr.io/distroless/static # ← крутиться як root за замовчуванням

Бери gcr.io/distroless/static:nonroot (UID 65532) за замовчуванням. Зменшує радіус ураження, якщо escape трапляється.

Real-world adoption

  • Внутрішні сервіси Google: distroless це дефолт для прод-image (Google створив).
  • Kubernetes-екосистема: багато CNCF-проектів шиппять distroless-image (Prometheus-компоненти, kube-state-metrics).
  • Banking / регульовані індустрії: distroless зменшує auditor-біль і CVE-management overhead.
  • Serverless: менші image = швидші cold-старти. AWS Lambda, Cloud Run виграють.

Питання для поглиблення

Q: Чи можу написати Dockerfile, що стартує FROM distroless напряму (без multi-stage)?


A: Технічно так, але не можеш нічого встановити (без shell, без apt). Distroless-base задумано як target multi-stage білду, не як builder.

Q: Яка різниця між static і base?


A: static має лише glibc і CA-certs. base додає busybox-static (кілька утиліт). Бери static для Go/Rust pure-static бінарів; base для C/C++, що потребують кількох утиліт.

Q: Чи distroless-image підписані?


A: Так, Google підписує через Cosign. Верифікуй через cosign verify gcr.io/distroless/base --certificate-identity=....

Q: Чи є Wolfi або Chainguard еквівалент?


A: Так, Chainguard Images це популярна альтернатива, що набирає ground. Wolfi-based, схожий мінімалізм, часто з ще швидшим CVE-patching. Варто оцінити для нових проектів.

Q: (Senior) Коли НЕ використовувати distroless?


A: Три кейси. (1) Твій app потребує shell у runtime (рідко; зазвичай smell). (2) Потребуєш багато tools у runtime, що потребуватимуть build кастомного minimal-image (краще: переосмисли app). (3) Команда ще не обладнана дебажити без docker exec sh — вкладай у tooling (sidecar-debug, observability) перш ніж форсувати distroless. Для greenfield Go/Rust сервісів distroless це no-brainer; для legacy-app з shell-dependent операціями мігруй поступово.

Приклади

Go у scratch (ще менший за distroless)

dockerfile
FROM golang:1.23-alpine AS build WORKDIR /src COPY . . RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /out/server ./cmd/server FROM scratch COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=build /out/server /server USER 65532:65532 ENTRYPOINT ["/server"]

scratch ще мінімальніший за distroless. Лише бінар + CA-certs. Фінальний image: приблизно розмір бінаря.

Distroless Node з healthcheck

dockerfile
FROM node:22-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY . . RUN npm run build FROM gcr.io/distroless/nodejs22:nonroot WORKDIR /app COPY --from=build /app/dist /app/dist COPY --from=build /app/node_modules /app/node_modules HEALTHCHECK --interval=30s --timeout=3s --retries=3 \ CMD ["node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode === 200 ? 0 : 1))"] CMD ["dist/server.js"]

Без curl у distroless; беремо Node-runtime як healthcheck-бінар. Той самий ефект, без extra-tools.

Production-like Python

dockerfile
FROM python:3.13-slim AS build WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir --prefix=/install -r requirements.txt COPY . . FROM gcr.io/distroless/python3:nonroot WORKDIR /app COPY --from=build /install /usr/local COPY --from=build /app /app USER nonroot:nonroot CMD ["app.py"]

Python-runtime + твій код + залежності, нічого більше. Фінальний image близько 50-150 MB залежно від dep-розміру, набагато менше за python:3.13-slim ~150-300 MB.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів