«По умолчанию Docker — удобный, но абсолютно сырой инструмент. Он даёт вам коробку, но оставляет думать, что она прочнее, чем есть. По умолчанию контейнер доверяет всему, что внутри и снаружи. Безопасность там, где вы её не строите, — отсутствует.»
Какой образ загружается под капотом
Когда вы запускаете docker run nginx, вы подписываетесь на доверие. Что именно? Последнюю версию тега latest с Docker Hub. Этот тег подвижен, его содержимое меняется со временем, и вчерашняя «стабильная» сборка может сегодня включать экспериментальные модули или зависимости с известными уязвимостями, ещё не попавшими в CVE-базы. Система тегов не гарантирует неизменность.
Кто собирал этот образ? Вы не знаете. Dockerfile, использованный для сборки, мог содержать команды вроде RUN apt-get update && apt-get install -y some-package, которая тянет десятки зависимостей, включая ненужные демоны или отладочные утилиты. Многие базовые образы, особенно ubuntu:latest или debian:buster, по умолчанию содержат пакетный менеджер, средства компиляции и даже shell-утилиты, которые в рабочем контейнере избыточны и расширяют поверхность атаки.
Привилегии внутри контейнера
По умолчанию процесс в контейнере запускается от имени root-пользователя (UID 0). Это даёт ему полный контроль над файловой системой контейнера. Если злоумышленнику удастся скомпрометировать приложение внутри (например, через уязвимость в веб-сервере), он получит root-привилегии в изолированном, но всё же опасном пространстве.
Контейнер не является виртуальной машиной, он использует общее с хостом ядро Linux. Уязвимость в ядре, позволяющая сбежать из контейнера (escape), сразу даёт атакующему root на хосте. Запуск от root внутри контейнера сокращает путь для такой атаки. Многие забывают, что даже если в Dockerfile указать USER nobody, базовый образ должен быть подготовлен: иметь созданного пользователя и корректно установленные права на файлы, иначе приложение просто не запустится.
Малоизвестный вектор: capabilities
Помимо проверки UID, ядро Linux управляет правами через механизм capabilities — набор из десятков отдельных привилегий (например, CAP_NET_ADMIN для управления сетью, CAP_SYS_ADMIN для администрирования системы). По умолчанию Docker запускает контейнер с сокращённым, но всё ещё значительным набором capabilities. Например, сохраняется CAP_NET_RAW, позволяющая работать с raw-сокетами, что может быть использовано для сетевых атак.
Полный список привилегий, которые остаются, неочевиден. Без явного указания флага --cap-drop=ALL и последующего добавления только необходимых (--cap-add=) контейнер сохраняет больше власти, чем требуется для большинства задач.
Сетевая изоляция и проброс портов
При создании контейнера Docker по умолчанию подключает его к сети bridge (docker0). Все контейнеры в одной сети видят друг друга по именам или IP-адресам. Если не настроены правила iptables/nftables отдельно, запущенный контейнер с сервисом на порту 5432 (PostgreSQL) может быть доступен не только для связанных с ним приложений, но и для любого другого контейнера в той же сети, который сможет подобрать или найти учётные данные.
Проброс портов на хост с помощью -p 80:80 открывает сервис не только локально. В большинстве дистрибутивов правила Docker по умолчанию разрешают входящие подключения с любого интерфейса, что может неожиданно выставить внутренний сервис в интернет, если хост имеет публичный IP.
Тома и проброс файлов хоста
Одна из самых опасных опций по умолчанию, это возможность смонтировать любой каталог с хоста внутрь контейнера. Команда docker run -v /home/user:/app/data ... кажется безобидной, но если скомпрометированное приложение в контейнере работает от root, оно получает возможность читать и писать в смонтированную директорию хоста без ограничений. Это прямой путь к утечке данных или подмене системных файлов.
Менее очевидный риск — монтирование Docker-сокета (-v /var/run/docker.sock:/var/run/docker.sock). Эта практика иногда используется для управления контейнерами изнутри других контейнеров (CI/CD). Но если доступ к сокету получит злоумышленник, он сможет выполнять любые Docker-команды от имени хоста, по сути получая контроль над всей средой.
Неявные зависимости и secrets
Контейнеры часто получают конфигурацию через переменные окружения (--env DB_PASSWORD=secret). Эти переменные видны любому процессу внутри контейнера и могут быть прочитаны через /proc/self/environ или выведены ошибкой в логах. По умолчанию Docker не шифрует и не маскирует эти данные.
Более того, многие образы включают в себя конфигурационные файлы с дефолтными паролями или ключами, оставшимися от этапа сборки. Проверка на наличие файлов вроде .env, config.json или id_rsa внутри образа перед его использованием — не стандартная процедура, а ответственность пользователя.
Что можно сделать сразу
Безопасность не добавляется одной командой, это набор практик. Вот с чего стоит начать, чтобы уйти от уязвимых настроек по умолчанию:
- Используйте конкретные теги образов, а не
latest. Например,nginx:1.25-alpine. Альпийские (Alpine) образы часто меньше и содержат меньше пакетов. - Запускайте приложение от непривилегированного пользователя. Создавайте его в Dockerfile (
RUN adduser -D myuser) и переключайтесь сUSER myuser. - Ограничивайте capabilities:
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE .... Определите минимальный набор для вашего приложения. - Изолируйте сеть. Создавайте отдельные пользовательские сети Docker для групп сервисов и контролируйте доступ между ними.
- Никогда не монтируйте Docker socket в контейнеры, не предназначенные для оркестрации. Для данных используйте named volumes вместо монтирования произвольных путей хоста.
- Для передачи секретов используйте специальные механизмы Docker Swarm или Kubernetes Secrets, либо монтируйте файлы с правами только на чтение.
- Регулярно сканируйте образы на уязвимости с помощью инструментов вроде Trivy или Docker Scout, даже для базовых образов.
Почему безопасность не стоит по умолчанию
Docker создавался как инструмент для разработчиков, где на первом месте была простота и скорость развёртывания. Жёсткие ограничения безопасности усложнили бы начальное знакомство. Парадокс в том, что контейнер стал промышленным стандартом, а менталитет использования остался «для разработки».
Безопасная конфигурация требует понимания не только Docker, но и механизмов Linux (namespaces, cgroups, capabilities, seccomp). Это высокий порог входа. Поэтому по умолчанию выбраны настройки, которые гарантированно работают для большинства приложений, ценой избыточных привилегий. Задача сместилась с «запустить» на «запустить безопасно», и это уже ответственность того, кто запускает.