Rootless Docker звучит как очевидное улучшение безопасности: если демон и контейнеры работают без root, значит риск ниже. Но как только дело доходит до реального использования, выясняется, что вместе с новым уровнем изоляции приходят ограничения по сети, storage, cgroups и привычным сценариям администрирования.
Эта статья — практический разбор: что именно меняется в модели безопасности, где rootless режим действительно помогает, а где неожиданно усложняет жизнь. И заодно ответ на вопрос, который возникает почти сразу: если нужен rootless — может, сразу взять Podman?
В статье
- Что такое rootless Docker
- Что меняется в модели безопасности
- Что ломается или усложняется
- Установка и smoke test
- Rootless и Compose в production
- Docker rootless vs Podman
- Где rootless оправдан, а где остаться на rootful
- Checklist: стоит ли вам переходить
Что такое rootless Docker
В обычной (rootful) установке Docker daemon (dockerd) работает от root. Это удобно — ему доступно всё: сеть, storage-драйверы, cgroups, монтирование. Но и цена высока: кто получил доступ к Docker-сокету, фактически получил root на хосте (docker run -v /:/host ... — и корень файловой системы у вас в контейнере).
Rootless Docker запускает и сам демон, и контейнеры от имени обычного непривилегированного пользователя. Ключевой механизм — user namespaces: внутри контейнера процесс видит себя как root (uid 0), но снаружи, на хосте, это обычный пользователь, а uid процессов контейнера отображаются в выделенный диапазон subordinate uid/gid из /etc/subuid и /etc/subgid.
flowchart TB
subgraph Rootful["Rootful Docker"]
RD["dockerd (root)"] --> RC["контейнер: root = root хоста"]
end
subgraph Rootless["Rootless Docker"]
UD["dockerd (user 1000)"] --> UC["контейнер: root внутри\n= user 1000 снаружи"]
end
style Rootful fill:#f3dcdc,stroke:#a05b5b
style Rootless fill:#dcecd8,stroke:#5b8a5e
style RD fill:#e8c8c8,stroke:#a05b5b
style RC fill:#e8c8c8,stroke:#a05b5b
style UD fill:#c9e4c5,stroke:#5b8a5e
style UC fill:#c9e4c5,stroke:#5b8a5e
Демон слушает не системный /var/run/docker.sock, а пользовательский сокет в $XDG_RUNTIME_DIR/docker.sock. Каждый пользователь может иметь свой изолированный экземпляр Docker.
Важно не путать два разных «root»:
- rootless Docker — без root работает сам демон;
- запуск контейнера без root внутри (
USERв Dockerfile,--user) — это про процесс внутри контейнера и работает в любой установке.
Это ортогональные вещи. Rootless Docker про то, от кого работает демон; USER appuser — хорошая практика независимо от того, rootful у вас Docker или rootless.
Что меняется в модели безопасности
Главная ценность rootless — сокращение blast radius при компрометации демона или сокета:
- уязвимость в
dockerdили утечка доступа к сокету больше не означает мгновенный root на хосте — атакующий ограничен правами обычного пользователя и его subuid-диапазоном; - побег из контейнера (container escape) упирается в те же ограничения: «root» сбежавшего процесса на хосте — это непривилегированный uid;
- на shared-хостах несколько пользователей могут запускать контейнеры, не мешая друг другу и не получая привилегий над системой.
Чего rootless не делает:
- это не замена остальной гигиене — seccomp, AppArmor/SELinux,
--cap-drop, read-only rootfs по-прежнему нужны; - это не защита от уязвимостей в самом приложении внутри контейнера;
- user namespace mapping — сильная, но не абсолютная граница; ядро всё равно общее.
Иными словами, rootless поднимает планку для атакующего, целящегося в хост через Docker, но не отменяет defense-in-depth.
Что ломается или усложняется
Здесь начинается реальная эксплуатация. Большинство ограничений — следствие того, что у непривилегированного процесса просто нет части возможностей.
Привилегированные порты (<1024). Обычный пользователь не может забиндить порт ниже 1024. -p 80:80 из коробки не сработает. Лечится либо публикацией на высокий порт (-p 8080:80), либо разрешением на хосте:
# единоразово
sudo sysctl net.ipv4.ip_unprivileged_port_start=80
# постоянно — в /etc/sysctl.d/
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-rootless.confСеть. Исходящий трафик контейнеров в rootless идёт через пользовательский сетевой стек — slirp4netns (раньше) или pasta (современный, быстрее и с сохранением исходного IP). Это даёт небольшой оверхед и иногда сюрпризы: по умолчанию контейнер видит не реальный source IP клиента, а адрес прокси. ICMP/ping внутри контейнера может требовать дополнительной настройки net.ipv4.ping_group_range.
Storage. Драйвер overlay2 в rootless работает нативно только на свежих ядрах (≈5.11+). На старых придётся использовать fuse-overlayfs — он медленнее и даёт другие характеристики по производительности I/O. Для нагруженных сборок это заметно.
cgroups и лимиты ресурсов. Управление --cpus, --memory требует cgroups v2 с делегированием через systemd. Без правильной delegation часть лимитов будет молча игнорироваться — особенно болезненно, если вы рассчитываете на них в production.
Прочее. Не все фичи доступны: некоторые сценарии с --privileged, отдельные варианты монтирования, часть сетевых режимов. Перед миграцией стоит проверить именно ваши сценарии, а не доверять «обычно работает».
Установка и smoke test
Rootless ставится поверх обычного пакета Docker отдельным скриптом — от имени целевого пользователя, без sudo:
# пакеты-зависимости: uidmap (newuidmap/newgidmap), dbus для user-сессии
sudo apt-get install -y uidmap dbus-user-session
# если ставили Docker из apt-репозитория Docker — нужен ещё пакет с rootless-скриптом
sudo apt-get install -y docker-ce-rootless-extras
# установка rootless-режима от обычного пользователя
dockerd-rootless-setuptool.sh install
# подсказанные переменные окружения — в ~/.bashrc
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sockПредусловие, о котором легко забыть: для текущего пользователя в /etc/subuid и /etc/subgid должно быть выделено не менее 65 536 subordinate uid/gid (официальное требование Docker). Обычно пакеты настраивают это автоматически, но на кастомных образах диапазон стоит проверить — без него dockerd-rootless-setuptool.sh откажется работать.
Чтобы демон жил без активной сессии (после выхода по SSH), включаем linger и автозапуск через user-systemd:
sudo loginctl enable-linger $(whoami)
systemctl --user enable --now dockerSmoke test — убедиться, что демон действительно непривилегированный:
docker info | grep -i rootless # rootless: true
docker run --rm alpine id # uid=0(root) ВНУТРИ контейнера
ps -o user= -p $(pgrep -n dockerd) # dockerd работает от вашего пользователя, не rootЖивой стенд, который наглядно показывает разницу rootful/rootless на владельце файлов в volume и на ограничении портов, — в digital-cookbook: docker/rootless. Поднимается одной командой (bash scripts/smoke.sh).
Rootless и Compose в production
Docker Compose с rootless работает — он общается с тем же пользовательским сокетом через DOCKER_HOST. Но есть нюансы, которые важны именно для production:
- порты — все
ports:вcompose.yamlподчиняются правилу <1024, ревизуйте маппинги (илиip_unprivileged_port_start); - bind mounts — каталоги с хоста монтируются с учётом uid-маппинга; файлы, созданные контейнером, на хосте будут принадлежать subuid, а не вашему пользователю напрямую. Подробнее о владельцах и правах — в статье про volumes и permissions;
- лимиты ресурсов —
deploy.resources/mem_limitсработают только при корректной cgroups v2 delegation; - автозапуск —
restart: alwaysопирается на user-systemd-сервисdocker; безenable-lingerконтейнеры остановятся при выходе пользователя.
Если вы уже выстроили production-Compose по этому материалу, переход на rootless — это в основном ревизия портов, томов и лимитов, а не переписывание с нуля.
Docker rootless vs Podman
Закономерный вопрос: если так хочется rootless — зачем городить его поверх Docker, когда есть Podman, у которого rootless заложен в дизайн?
Ключевая разница — архитектура:
- Docker — клиент-серверный: есть постоянный демон
dockerd, контейнеры — его дочерние процессы. Rootless здесь надстройка над демонной моделью. - Podman — daemonless: нет постоянного демона, каждый контейнер запускается как обычный дочерний процесс (
fork-exec) под управлениемconmon. Rootless — режим по умолчанию, а не опция.
| Docker rootless | Podman | |
|---|---|---|
| Демон | есть (rootless dockerd) |
нет (daemonless) |
| Rootless | надстройка | по умолчанию |
| CLI | docker |
podman (CLI-совместим) |
| Compose | Docker Compose | podman compose / docker-compose через сокет |
| systemd-интеграция | user-сервис демона | нативная (Quadlet, генерация unit’ов) |
| Pods (как в K8s) | нет | есть |
Практический ориентир:
- остаётесь на Docker rootless, если у вас уже выстроена экосистема вокруг Docker (Compose-файлы, CI, привычки команды), а rootless нужен точечно для снижения рисков;
- смотрите на Podman, если затеваете окружение с нуля, цените отсутствие демона, планируете тесную интеграцию с systemd или думаете в сторону Kubernetes (pods, генерация манифестов).
Podman не «лучше» или «хуже» — он про другую модель. Для темы rootless он важен как напоминание: иногда правильный ответ на «как сделать Docker безопаснее» — это «а нужен ли здесь именно Docker».
Где rootless оправдан, а где остаться на rootful
Rootless имеет смысл:
- домашний lab и dev-окружения, где безопасность хоста важнее предельной производительности;
- user-space окружения и CI-раннеры, где не хочется давать сборкам root;
- отдельные сервисные хосты с повышенным вниманием к изоляции и multi-tenant сценарии.
Лучше остаться на rootful:
- нужен максимум совместимости и все фичи (сложная сеть,
--privileged, специфичные mount’ы); - инфраструктура сильно завязана на привычные сетевые сценарии и реальный source IP без танцев с
pasta; - команда пока не готова разбираться с subuid, cgroups delegation и storage-нюансами — поспешный переход добавит инцидентов, а не безопасности.
Checklist: стоит ли вам переходить
- Что вы защищаете — хост от компрометации демона/сокета? Если да, rootless бьёт в цель.
- Нужны ли вам порты <1024 и готовы ли вы настроить
ip_unprivileged_port_start? - Достаточно ли свежее ядро для нативного
overlay2(иначе — fuse-overlayfs и просадка I/O)? - Настроена ли cgroups v2 delegation, если вы полагаетесь на лимиты ресурсов?
- Проверены ли именно ваши сетевые сценарии (source IP, ping, проброс портов)?
- Не проще ли для вашего случая взять Podman вместо rootless-надстройки над Docker?
Если на пункт 1 ответ «не знаю» — сначала проясните модель угроз. Rootless — это инструмент против конкретного риска, а не галочка «стало безопаснее».
Комментарии