Безпека Docker-образів: non-root, мінімальні базові образи і сканування в CI
Практичний мінімум безпеки контейнерів: від вибору базового образу до сканування вразливостей, підпису образів і обмежень під час запуску.
Контейнер — не віртуальна машина. Він ділить ядро з хостом, і погано зібраний образ, запущений з зайвими правами, — короткий шлях від вразливості в бібліотеці до компрометації сервера. Хороша новина: більшість ризиків закривається кількома простими звичками.
1. Мінімальний базовий образ
Кожен пакет в образі — потенційна вразливість. Порівняння типових варіантів:
| Базовий образ | Розмір | Що всередині |
|---|---|---|
ubuntu:24.04 |
~80 МБ | Повна ОС, shell, apt |
python:3.13 |
~1 ГБ | Debian + компілятори |
python:3.13-slim |
~130 МБ | Мінімальний Debian |
alpine |
~8 МБ | musl, busybox |
| distroless / chainguard | 2–50 МБ | Лише рантайм, без shell і пакетного менеджера |
Для компільованих мов (Go, Rust) — distroless/static або навіть scratch. Для Python і Node — -slim або distroless з multi-stage збіркою.
ПорадаВідсутність shell в образі — це плюс до безпеки: зловмиснику, який отримав виконання коду, набагато складніше щось робити далі. Для налагодження використовуйте
kubectl debugз окремим образом-інструментом.
2. Не root
FROM python:3.13-slim
RUN useradd --create-home --uid 10001 app
WORKDIR /home/app
COPY --chown=app:app . .
USER 10001Числовий UID (а не ім'я) дозволяє Kubernetes перевірити runAsNonRoot без читання /etc/passwd.
3. Секрети не в образі
- жодних
.env, ключів іARG PASSWORD=...в образі — шари зберігають усе; .dockerignoreз.env*,*.pem,.git;- секрети під час збірки — через
RUN --mount=type=secret; - секрети під час роботи — зі сховища секретів (Vault, AWS Secrets Manager) або змінних середовища оркестратора.
# Перевірити, що в образі немає секретів
trivy image --scanners secret myapp:1.4.24. Сканування вразливостей у CI
# фрагмент GitHub Actions
- name: Сканування образу
# у реальному пайплайні фіксуйте action за SHA коміту, а не за тегом
uses: aquasecurity/trivy-action@v0.36.0
with:
image-ref: ghcr.io/acme/api:${{ github.sha }}
severity: CRITICAL,HIGH
ignore-unfixed: true # не блокувати на вразливостях без виправлення
exit-code: "1"ignore-unfixed— інакше пайплайн стоятиме через CVE, для яких ще немає патча;- винятки — у файлі
.trivyignoreз коментарем, чому вразливість не застосовна і до якої дати переглянути; - скануйте не лише при збірці, а й за розкладом: нові CVE з'являються для вже задеплоєних образів.
Ланцюжок постачання — теж ваша поверхня атакиУ березні 2026 року зловмисники перезаписали десятки тегів версій самого
trivy-actionна шкідливий код (advisory GHSA-69fq-xp46-6x23). Пайплайни, що посилались на тег, запускали чужий код із доступом до секретів CI. Фіксуйте сторонні actions за SHA коміту і переглядайте оновлення як звичайні зміни коду.
УвагаСотня «HIGH» у звіті, на які всі давно не дивляться, — гірше, ніж десять, які розібрано. Почніть з базового образу: оновлення
FROMчасто прибирає 80% знахідок за один коміт.
5. SBOM і підпис образів
SBOM (Software Bill of Materials) — перелік усіх компонентів образу. Коли з'являється нова критична вразливість, SBOM за хвилини відповідає на питання «де в нас використовується ця бібліотека?».
# Згенерувати SBOM у форматі CycloneDX
trivy image --format cyclonedx --output sbom.json ghcr.io/acme/api:1.4.2
# Підписати образ без довгоживучих ключів (keyless через OIDC у CI)
cosign sign ghcr.io/acme/api@sha256:3f1c...
# Перевірити підпис перед деплоєм
cosign verify ghcr.io/acme/api@sha256:3f1c... \
--certificate-identity-regexp 'https://github.com/acme/api/.*' \
--certificate-oidc-issuer https://token.actions.githubusercontent.comУ Kubernetes політики допуску (Kyverno, Sigstore policy-controller) можуть забороняти запуск непідписаних образів.
6. Обмеження під час запуску
# Kubernetes securityContext
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault# Те саме для docker run
docker run --read-only --tmpfs /tmp \
--cap-drop ALL --security-opt no-new-privileges \
--user 10001 myapp:1.4.2ОбережноНіколи не запускайте робочі контейнери з
--privilegedі не монтуйте/var/run/docker.sockу контейнер застосунку. Доступ до Docker-сокета — це фактично root на хості.
Чекліст
- мінімальний базовий образ, оновлюється щонайменше раз на місяць;
USERз числовим UID, не root;.dockerignore, жодних секретів у шарах;- сканування на CRITICAL/HIGH у CI і за розкладом;
- SBOM і підпис для образів, що йдуть у продакшен;
- read-only файлова система,
drop ALLcapabilities, без privileged.
Чому монтування /var/run/docker.sock у контейнер небезпечне?
Через сокет можна керувати Docker-демоном хоста: запустити новий контейнер з --privileged, змонтувати кореневу файлову систему хоста і отримати повний контроль над сервером. Тобто компрометація такого контейнера дорівнює root на хості.
Збережіть статтю собі
Увійдіть, щоб імпортувати її у власні нотатки, відмічати вивчене, ставити лайки й питати спільноту. Реєстрація безкоштовна: вхід через Google займає кілька секунд.
Увійти через Google Інші способи входу