Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке distroless образи і яку перевагу вони дають?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Distroless image** містять лише те, що твоєму застосунку реально потрібно для запуску, мовний runtime і твій бінар. **Без shell, без package manager, без apt, без busybox.** Результат: малі (часто <50 MB) з near-zero attack surface. ```dockerfile 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"] ``` **Головне:** менші, безпечніші, швидші pull. Trade-off: жодного `docker exec sh`, debug потребує `:debug`-варіантів. Найкраще пасує для прод runtime image скомпільованих або інтерпретованих застосунків.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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-certs | Static-бінарі (Go, Rust) | | `gcr.io/distroless/base` | static + glibc + busybox-static | Переважно static-бінарі з малими deps | | `gcr.io/distroless/cc` | base + libgcc | C/C++ бінарі з compiler-runtime | | `gcr.io/distroless/python3` | Python-runtime | Python-app | | `gcr.io/distroless/nodejs22` | Node 22-runtime | Node-app | | `gcr.io/distroless/java21` | Java 21-runtime | JVM-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 | | Alpine | Distroless | |---|---|---| | Має shell? | Так (busybox sh) | Ні | | Має package-manager? | Так (apk) | Ні | | Розмір base | ~7 MB | 2-50 MB залежно від варіанту | | Легко debug? | Так | Ні (потрібен :debug-варіант) | | Multi-stage friendly? | Так | Так | | Glibc чи musl? | musl | glibc | | Зрепортовані 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.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.