// Engineering Log

Безопасная разработка: Часть 6 — Безопасность контейнеров

Опубликовано 22.09.2026

// Быстрый маршрут

Эта статья относится к теме Деплой и стабильная работа.

Контейнер изолирует приложение от хоста, но изоляция не абсолютна: все контейнеры используют одно ядро. Уязвимость в образе, процесс с правами root или лишние привилегии превращают ошибку в приложении в проблему для всего сервера. Эта часть — о том, что делать с уже собранным образом и как его запускать. Правильная сборка Dockerfile (кеш, многоэтапная сборка, закрепление по digest) разобрана в части 5, проверка зависимостей — в части 4.

Откуда берутся риски

Образ состоит из слоёв: базовый образ с пакетами дистрибутива, зависимости приложения, сам код. Уязвимости чаще всего приходят не из вашего кода, а из базового образа: полный python:3.13 или node:22 содержит компилятор, пакетный менеджер, оболочку и сотни библиотек, которые приложению в работе не нужны. Каждая из них — потенциальная уязвимость и готовый инструмент для атакующего, если он попал внутрь контейнера.

Вторая группа рисков — параметры запуска: процесс от root, запись в любую часть файловой системы, полный набор возможностей ядра (capabilities), отсутствие ограничений по памяти и числу процессов.

Минимальный базовый образ

Чем меньше в образе, тем меньше поверхность атаки и находок у сканера.

  • -slim (python:3.13-slim, node:22-slim) — Debian без инструментов сборки и документации. Разумный вариант по умолчанию: совместимость с glibc сохраняется, размер заметно меньше полного образа.
  • Alpine — очень маленький образ на musl libc. Подходит для статически собранных программ; для Python и Node.js с нативными модулями возможны проблемы совместимости и более долгая сборка.
  • Distroless от Google — в образе только среда выполнения, без оболочки и пакетного менеджера. Актуальные образы основаны на Debian 13: gcr.io/distroless/static-debian13 для статических бинарников (Go, Rust), gcr.io/distroless/python3-debian13, gcr.io/distroless/nodejs22-debian13 и другие. У большинства есть тег :nonroot — процесс сразу запускается не от root, и тег :debug с оболочкой busybox для отладки.

Не используйте версии без поддержки: Python 3.9 и Node.js 18 больше не получают исправлений безопасности. Для новых образов — Python 3.12 или 3.13, Node.js 22 или 24.

Отсутствие оболочки в distroless-образе — одновременно защита и неудобство: docker exec -it … sh не сработает. Для разбора проблем используйте вариант :debug в тестовой среде или отдельный отладочный контейнер, а не добавляйте оболочку в рабочий образ.

Запуск не от root

По умолчанию процесс в контейнере работает от root. Если атакующий выйдет за пределы приложения, у него будут максимальные права внутри контейнера, а при уязвимости в среде выполнения — шанс получить root на хосте.

В Dockerfile создайте пользователя и переключитесь на него:

dockerfile
FROM python:3.13-slim
RUN useradd --system --uid 10001 --no-create-home app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["python", "main.py"]

Для distroless достаточно тега :nonroot. Пользователя можно задать и при запуске: docker run --user 10001:10001 …. Приложению без root нельзя слушать порты ниже 1024 — используйте порт 8080 и публикуйте его наружу как нужно.

Ограничения при запуске

Параметры docker run (в Compose — одноимённые ключи сервиса) закрывают большую часть типовых путей повышения привилегий:

bash
docker run -d --name app \
  --read-only --tmpfs /tmp \
  --cap-drop=ALL \
  --security-opt no-new-privileges \
  --memory 512m --cpus 1 --pids-limit 200 \
  --user 10001:10001 \
  registry.example.ru/app@sha256:...
  • --read-only монтирует корневую файловую систему только для чтения: атакующий не сможет подменить файлы приложения или записать инструменты. Каталоги, куда приложение действительно пишет, подключаются отдельно: --tmpfs /tmp или том для данных.
  • --cap-drop=ALL убирает все возможности ядра. Если приложению что-то нужно — например, NET_BIND_SERVICE для порта 80, — добавьте ровно это через --cap-add.
  • --security-opt no-new-privileges запрещает процессам получать новые привилегии, в том числе через su, sudo и файлы с битом setuid.
  • --memory, --cpus, --pids-limit ограничивают ресурсы: утечка памяти или fork-бомба в одном контейнере не положит весь сервер.

Никогда не запускайте рабочие контейнеры с --privileged и не монтируйте в них сокет Docker (/var/run/docker.sock): доступ к сокету равен правам root на хосте.

Seccomp и rootless-режим

Seccomp фильтрует системные вызовы. Профиль Docker по умолчанию работает как список разрешённых вызовов и блокирует около 44 из более чем 300 — те, что нужны для атак на ядро и почти не нужны обычным приложениям. Документация Docker не рекомендует его менять; главное — не отключать его ключом --security-opt seccomp=unconfined, который иногда советуют в инструкциях «чтобы заработало».

Rootless-режим запускает не только контейнеры, но и сам демон Docker от обычного пользователя. Даже при выходе из контейнера атакующий получает права этого пользователя, а не root. Для установки нужны утилиты newuidmap и newgidmap (пакет uidmap) и не меньше 65 536 подчинённых идентификаторов в /etc/subuid и /etc/subgid; затем от имени пользователя выполняется dockerd-rootless-setuptool.sh install. Rootless-режим сужает часть возможностей (сеть, привилегированные порты), поэтому его стоит проверить на своём стеке перед переходом.

Сканирование образов

Собранный образ нужно проверять на известные уязвимости до выкладки и регулярно после неё: новые CVE находят в уже опубликованных пакетах. Тот же Trivy, что проверяет зависимости (см. часть 4), сканирует и образ целиком:

bash
trivy image --severity HIGH,CRITICAL --exit-code 1 registry.example.ru/app:1.4.2

С --exit-code 1 сканирование останавливает пайплайн при находках высокого уровня. Результаты сканирования образа на -slim или distroless обычно на порядок короче, чем на полном образе, — это самый дешёвый способ сократить список уязвимостей.

Подпись и проверка образов

Подпись гарантирует, что на сервер попадает тот образ, который собрал ваш CI, а не подменённый в реестре. Стандартный инструмент — cosign из проекта Sigstore:

bash
cosign generate-key-pair
cosign sign --key cosign.key registry.example.ru/app@sha256:...
cosign verify --key cosign.pub registry.example.ru/app@sha256:...

Подписывать и проверять нужно по digest, а не по тегу: тег можно переназначить на другой образ. Cosign поддерживает и режим без собственных ключей, с подтверждением через OIDC-провайдера. Проверка подписи имеет смысл, только если она встроена в выкладку: скрипт деплоя или контроллер в Kubernetes отказывается запускать неподписанный образ.

Чек-лист

  1. Базовый образ -slim или distroless, поддерживаемая версия среды выполнения, закрепление по digest.
  2. USER не root; порт приложения выше 1024.
  3. Запуск с --read-only, --cap-drop=ALL, no-new-privileges, лимитами памяти, CPU и процессов.
  4. Никакого --privileged, сокета Docker внутри контейнера и seccomp=unconfined.
  5. Сканирование образа в CI и регулярно для уже работающих образов.
  6. Подпись образа в CI и проверка подписи при выкладке.
  7. Где возможно — rootless-режим Docker.

Типичные ошибки

  • «Контейнер — это песочница». Все контейнеры делят ядро хоста; изоляция держится на правах и ограничениях, которые вы задали.
  • Root по умолчанию. Самая частая находка аудита — и самая простая в исправлении.
  • Отключённый seccomp или --privileged для отладки, оставшийся в рабочей конфигурации.
  • Сканирование только при сборке. Образ, собранный полгода назад, мог обрасти десятком новых CVE.
  • Тег latest в рабочем окружении. Непонятно, что именно запущено, и нельзя проверить подпись.

// Похожая задача

Если у вас похожая ситуация

Эта статья относится к одной из рабочих тем. Можно продолжить чтение по теме, перейти на главную, чтобы понять, чем я занимаюсь, или сразу открыть услуги.

Тема статьи

Деплой и стабильная работа

Docker, CI/CD, релизы, мониторинг, observability и разбор инцидентов.

Часто с этим приходят

  • Настроить деплой без ручных действий и хаоса
  • Подключить мониторинг, алерты и базовую observability
  • Разобрать инциденты и стабилизировать production

// Следующий шаг

Если вам нужна не только статья, а помощь по этой теме, удобнее сразу перейти в услугу. Главная и подборка материалов остаются рядом.

Открыть услуги

// Reviews

Отзывы по теме

Опыт сотрудничества оставил максимально позитивное впечатление, в первую очередь профессионализмом и подходом к решению возникающих проблем.

Опыт сотрудничества оставил максимально позитивное впечатление, в первую очередь профессионализмом и подходом к решению возникающих проблем.

mendarinno384

Jitsi meet: персональный zoom, настройка jitsi meet в docker и на VPS

11.11.2025 · ★ 5/5

Была задача наладить работу n8n, redis и базы данных. Заказывал раньше у другого исполнителя, постоянно все ломалось. Заказал у Михаила, на следующий же день все стало работать быстро, как часы!

Была задача наладить работу n8n, redis и базы данных. Заказывал раньше у другого исполнителя, постоянно все ломалось. Заказал у Михаила, на следующий же день все стало работать быстро, как часы!

christ_media

N8n установка на ваш vps сервер. Настройка n8n, docker, ai, telegram

24.09.2025 · ★ 5/5

Опытный покупатель

ladohinpy

N8n установка на ваш vps сервер. Настройка n8n, docker, ai, telegram

25.08.2025 · ★ 5/5

Михаил выполнил настройку очередного VPS. Быстро, профессионально обходя определенные ограничение хостинг провайдеров.

Михаил выполнил настройку очередного VPS. Быстро, профессионально обходя определенные ограничение хостинг провайдеров.

NadoBy

NadoBy

N8n установка на ваш vps сервер. Настройка n8n, docker, ai, telegram

12.08.2025 · ★ 5/5

Освоившийся покупатель

// Contact

Нужна помощь?

Свяжись со мной и я помогу решить проблему

Написать в Telegram

Отвечаю в течение рабочего дня (03:00–13:00 GMT)

Или оставьте заявку здесь:

Подтвердите, что вы не бот.

Написать и получить быстрый ответ