Контейнер облегчает доставку приложения, но не создаёт автоматическую границу доверия. Уязвимый базовый образ, секрет в Dockerfile, доступный Docker socket или контейнер с лишними capabilities способны привести к компрометации хоста. Безопасность начинается в репозитории и продолжается до runtime и обновления production.
Современный подход рассматривает контейнер как артефакт цепочки поставки: исходный код, зависимости, builder, registry, подпись, оркестратор и хост должны иметь проверяемое происхождение и минимальные права.
Модель угроз контейнерного проекта
- вредоносный или заброшенный базовый образ;
- уязвимые системные и языковые зависимости;
- секреты в слоях образа или истории Git;
- подмена образа между сборкой и деплоем;
- запуск от root и лишние Linux capabilities;
- монтирование Docker socket;
- неограниченная сеть между контейнерами;
- отсутствие лимитов ресурсов и журналирования.
Фиксируйте происхождение базового образа
Используйте официальный или внутренний доверенный registry. В production фиксируйте образ по digest:
docker pull nginx@sha256:...
docker image inspect nginx@sha256:...
Тег удобен человеку, но может указывать на другое содержимое. Digest связывает deployment с конкретным артефактом. При этом pinning не отменяет обновления: процесс должен регулярно создавать новый проверенный digest.
Multi-stage build и минимальный runtime
FROM golang:1.24-bookworm AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /out/app ./cmd/app
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
Компилятор и менеджер пакетов остаются в build-stage, а runtime содержит только нужный бинарник. Для Python, PHP и Java выбирайте минимальный, но обслуживаемый образ. «Пустой» образ не всегда лучше: если команда не умеет его диагностировать и обновлять, операционный риск растёт.
Воспроизводимые зависимости
Используйте lock-файлы и проверку контрольных сумм. Не выполняйте curl | sh в Dockerfile и не скачивайте «последнюю» версию без верификации. Сборка должна быть повторяемой, а источник каждого пакета — известным.
Генерируйте SBOM для образа. Он показывает компоненты и помогает быстро определить, затрагивает ли новая уязвимость ваши контейнеры.
Сканирование и политика допуска
Сканируйте образ после сборки и перед deployment. Разделяйте найденные CVE по доступности исправления, эксплуатируемости и реальному использованию компонента. Политика может запрещать критические уязвимости с доступным патчем и требовать документированное исключение для остальных.
Одного сканирования мало. Образ, безопасный сегодня, может получить новую CVE завтра, поэтому registry или отдельная задача должны пересканировать уже опубликованные артефакты.
Подпись образов
Подписывайте production-образы после доверенной CI-сборки и проверяйте подпись перед запуском. В deployment используйте digest и правила допуска: разрешены только образы из утверждённого registry, подписанные нужной identity.
Ключ подписи не должен храниться рядом с Dockerfile. Современные схемы могут использовать краткоживущую identity CI вместо постоянного приватного ключа.
Секреты не являются build-аргументами
ARG и ENV могут попасть в историю образа, логи и метаданные. Для доступа к приватным зависимостям используйте BuildKit secrets, а runtime-секреты получайте из secret manager или защищённого механизма оркестратора.
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
После утечки удаление секрета из последнего слоя не помогает: он может остаться в предыдущем слое и кэше. Секрет нужно отозвать, выпустить заново и очистить историю/registry согласно процедуре.
Минимальные права runtime
Контейнер приложения запускайте от непривилегированного UID. Файловую систему делайте read-only, оставляя write-доступ только нужным каталогам. Удаляйте capabilities и запрещайте повышение привилегий:
docker run --read-only --cap-drop=ALL --security-opt=no-new-privileges:true --user 10001:10001 --tmpfs /tmp:rw,noexec,nosuid,size=64m myapp@sha256:...
Добавляйте отдельную capability только после подтверждения необходимости. Не используйте --privileged для исправления ошибки доступа — он почти стирает границу между контейнером и хостом.
Rootless Docker
Rootless-режим снижает последствия компрометации daemon и контейнера, но имеет ограничения по сети, storage drivers и низким портам. Протестируйте производительность и совместимость. Rootless не отменяет уязвимости ядра и контроль конфигурации.
Почему опасен Docker socket
Монтирование /var/run/docker.sock внутрь контейнера фактически даёт возможность управлять daemon: запускать привилегированные контейнеры, монтировать файловую систему хоста и получать секреты. Если инструменту нужен API Docker, используйте ограниченный proxy или отдельный изолированный worker.
Сеть и публикация портов
Не публикуйте порт, если он нужен только соседнему контейнеру. Создавайте отдельные сети для frontend, backend и данных. База должна быть доступна приложению, но не всему хосту и интернету.
services:
db:
image: postgres@sha256:...
networks: [backend]
app:
image: app@sha256:...
networks: [frontend, backend]
proxy:
image: nginx@sha256:...
ports: ["443:443"]
networks: [frontend]
Лимиты, healthcheck и журналы
Задайте лимиты CPU, RAM и процессов, чтобы ошибка или атака не исчерпала ресурсы хоста. Healthcheck должен проверять полезную функцию, а не только существование процесса. Журналы отправляйте централизованно и ограничивайте локальную ротацию.
Чеклист CI/CD
- проверка исходного кода и Dockerfile;
- сборка из фиксированных зависимостей;
- создание SBOM;
- сканирование образа;
- подпись доверенной CI identity;
- публикация в закрытый registry;
- deployment по digest;
- проверка подписи и политики;
- наблюдение и регулярное пересканирование.
Hardening Docker-хоста
Контейнеры наследуют безопасность ядра и daemon. Хост должен выполнять одну понятную роль, регулярно обновляться и иметь минимальный набор пакетов. Доступ к группе docker фактически равен административному: её участник может запустить контейнер с монтированием корневой файловой системы.
getent group docker
docker info
docker system df
ss -tulpn
Не публикуйте Docker daemon по TCP без строгой взаимной TLS-аутентификации и сетевого ограничения. Для большинства одиночных серверов удалённый API вообще не нужен.
Безопасный Compose-файл
В Compose явно задавайте пользователя, read-only filesystem, tmpfs, dropped capabilities, лимиты и healthcheck. Не монтируйте весь проект или домашний каталог в production-контейнер. Каждый bind mount расширяет доступ приложения к хосту.
services:
app:
image: registry.example/app@sha256:...
user: "10001:10001"
read_only: true
cap_drop: ["ALL"]
security_opt: ["no-new-privileges:true"]
tmpfs:
- /tmp:size=64m,noexec,nosuid
pids_limit: 200
restart: unless-stopped
Обновление без неконтролируемого latest
Обновление должно создавать новый immutable-артефакт, проходить тесты, сканирование и подпись, после чего меняется digest в deployment. Автоматическое подтягивание latest прямо в production удобно, но лишает команду контроля над моментом и содержимым изменения.
Подготовьте rollback на предыдущий digest и учитывайте миграции базы. Откат контейнера не откатывает необратимую схему данных, поэтому миграции проектируются совместимыми с двумя версиями приложения.
Runtime-наблюдение
Собирайте события запуска контейнеров, изменения образов, ошибки healthcheck, OOM, перезапуски и необычные сетевые соединения. Сравнивайте фактический runtime с утверждённым deployment: неожиданный контейнер или интерактивный shell на production требует проверки.
Журнал приложения должен содержать request ID и бизнес-события, но не секреты. Логи Docker ограничивайте ротацией, иначе один шумный контейнер заполнит диск хоста.
Резервное копирование данных
Образ контейнера не является резервной копией. Отдельно защищайте volumes, базы, загруженные пользователями файлы и секреты конфигурации. Проверяйте восстановление на новом хосте с тем же зафиксированным образом. Документируйте RTO/RPO и порядок запуска зависимостей.
FAQ
Нужен ли Kubernetes для безопасного Docker?
Нет. Для небольшого проекта Compose и systemd проще. Безопасность зависит от контроля образов, прав и хоста.
Можно ли использовать latest?
Для локальной разработки допустимо, но production должен быть воспроизводимым — используйте digest или неизменяемый version tag с проверкой.
Контейнер заменяет виртуальную машину?
Нет. Контейнеры обычно разделяют ядро хоста и имеют другую модель изоляции.
Для Python, PHP, ботов и pet-проектов мы рекомендуем Beget — SSH, Python, cron и поддержка 24/7 от 150 ₽/мес.
Автоматизация, CRM, Telegram-боты, деплой и техподдержка 24/7.
Телефон: 8 918 532 81 08 · Заказать услугу

