1.6. Rootless Docker
Цели
После этого материала вы сможете:
- объяснить, чем rootless mode отличается от обычного и что именно он даёт;
- подготовить систему:
uidmap, диапазоныsubuidиsubgid; - установить и запустить rootless Docker через
dockerd-rootless-setuptool.sh; - переключаться между системным и rootless daemon через
DOCKER_HOST; - назвать ограничения rootless mode и оценить, приемлемы ли они для вашей задачи;
- опубликовать порт ниже 1024 в rootless mode;
- полностью удалить Docker вместе с данными.
Предварительные знания
- 1.5. Docker socket и группа
docker— почему это вообще нужно; - 1.3. Docker daemon — управление службой;
- понимание UID и GID.
Ключевые термины
| Термин | Объяснение |
|---|---|
rootless mode | Режим, в котором daemon и containers работают от обычного пользователя без прав root |
user namespace | Namespace, отображающий UID внутри на другие UID снаружи. Основа rootless mode |
subuid / subgid | Диапазоны UID и GID, выделенные пользователю для использования в user namespace |
newuidmap / newgidmap | Утилиты из пакета uidmap, настраивающие отображение UID. Единственные компоненты с повышенными правами |
RootlessKit | Компонент, создающий user namespace и пробрасывающий порты в rootless mode |
slirp4netns / pasta | Реализации сетевого стека в пространстве пользователя |
lingering | Режим systemd, при котором пользовательские службы работают без активной сессии |
Теория
Что даёт rootless mode
В обычном режиме dockerd работает от root. Любой, кто может отправить ему команду, получает права root на машине — это разбиралось в предыдущем уроке.
В rootless mode daemon работает от вашего пользователя. Внутри container процесс может считать себя root (UID 0), но снаружи, с точки зрения ядра host, это ваш обычный UID или один из выделенных вам subordinate UID.
Обычный режим Rootless режим
┌─────────────────────────┐ ┌─────────────────────────┐
│ container: UID 0 (root) │ │ container: UID 0 (root) │
└───────────┬─────────────┘ └───────────┬─────────────┘
│ │ user namespace
▼ тот же UID ▼ отображает UID
┌─────────────────────────┐ ┌─────────────────────────┐
│ host: UID 0 (root) │ │ host: UID 165536 │
│ полные права │ │ обычный пользователь │
└─────────────────────────┘ └─────────────────────────┘
Практическое следствие: побег из container в rootless mode даёт права обычного пользователя, а не root. Это качественное, а не количественное улучшение.
Как это работает
Ядро Linux позволяет непривилегированному пользователю создать user namespace, где он будет иметь UID 0. Но внутри такого namespace доступен только один UID — его собственный. Для containers нужен диапазон: приложениям требуются разные UID.
Диапазон выделяется заранее и записывается в /etc/subuid и /etc/subgid. Утилиты newuidmap и newgidmap (они установлены с битом setuid) настраивают отображение по этим файлам. Это единственные компоненты схемы, требующие повышенных прав, и они делают ровно одну строго ограниченную операцию.
Дальше RootlessKit создаёт namespaces, а dockerd работает уже внутри них.
Что теряется
Rootless mode — компромисс. Часть возможностей ядра действительно требует root, и обойти это нельзя.
| Ограничение | Подробности |
|---|---|
| Storage drivers | Поддерживаются overlay2 (ядро 5.11+), fuse-overlayfs (ядро 4.18+), btrfs (ядро 4.18+) и vfs. vfs работает везде, но неэффективен: копирует слои целиком |
| Cgroups | Ограничение ресурсов (--cpus, --memory, --pids-limit) работает только на cgroup v2 с systemd. Обычно по умолчанию делегируются только контроллеры memory и pids; для cpu и io нужна дополнительная настройка делегирования |
| AppArmor | Не поддерживается |
| Checkpoint / restore | Не поддерживается |
| Overlay network | Не поддерживается (актуально для Swarm) |
| SCTP-порты | Публикация не поддерживается |
| Порты ниже 1024 | По умолчанию недоступны; требуется дополнительная настройка (см. ниже) |
| Сеть | Работает в пространстве пользователя через slirp4netns или pasta, что медленнее, чем сетевой стек ядра. По умолчанию исходный IP-адрес клиента не сохраняется |
| Доступ к устройствам host | Ограничен правами вашего пользователя |
Когда rootless оправдан
| Ситуация | Оценка |
|---|---|
| Общий сервер разработки с несколькими пользователями | Оправдан. Главный сценарий применения |
| Выполнение недоверенного кода (CI-раннеры) | Оправдан, хотя одного rootless недостаточно — см. раздел 12 |
| Личная рабочая машина | По желанию. Выигрыш есть, но ограничения могут мешать |
| Production-сервис с требованиями к производительности сети | Обычно не подходит из-за сетевого стека в пространстве пользователя |
| Изучение материала этого курса | Не рекомендуется как основной режим: часть упражнений (разделы 07, 08, 17) даёт другой результат |
Рекомендация курса. Пройдите курс на обычной установке, а rootless настройте параллельно и попереключайтесь между ними. Оба daemon могут сосуществовать: у них разные сокеты и разные каталоги данных.
Внутренний механизм
Диапазоны subuid и subgid
Файл /etc/subuid содержит строки вида:
evg:100000:65536
Читается так: пользователю evg выделены 65 536 подчинённых UID начиная со 100000, то есть диапазон 100000–165535.
Отображение внутри user namespace выглядит следующим образом:
| UID в container | UID на host |
|---|---|
| 0 (root) | 1000 (ваш UID) |
| 1 | 100000 |
| 2 | 100001 |
| ... | ... |
| 65536 | 165535 |
Практическое следствие для раздела 07: файл, созданный внутри container от UID 1000, будет принадлежать на host UID 100999. Это меняет всю логику работы с правами доступа при bind mount.
Требование — не менее 65 536 UID и GID на пользователя. Современные дистрибутивы создают эти записи автоматически при добавлении пользователя.
Два сокета
Rootless daemon слушает другой socket:
Обычный: /var/run/docker.sock
Rootless: /run/user/<UID>/docker.sock
Переменная DOCKER_HOST указывает CLI, к какому daemon подключаться. Это позволяет держать оба и переключаться между ними одной командой.
Каталоги данных тоже разные: rootless использует ~/.local/share/docker. Образы, containers и volumes у двух daemon не общие.
Lingering
По умолчанию systemd завершает пользовательские службы при выходе пользователя из системы. Для сервера это неприемлемо: containers остановятся, как только вы закроете SSH-сессию.
Команда loginctl enable-linger включает режим, при котором пользовательские службы работают независимо от сессий и запускаются при загрузке системы.
Команды и примеры
Шаг 1. Проверка предпосылок
# наличие утилит отображения UID
which newuidmap newgidmap
/usr/bin/newuidmap
/usr/bin/newgidmap
Если команд нет:
sudo apt install -y uidmap
Проверка диапазонов:
grep "^$USER:" /etc/subuid /etc/subgid
/etc/subuid:evg:100000:65536
/etc/subgid:evg:100000:65536
Требуется не менее 65 536 в каждом файле. Если записей нет, добавьте:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$USER"
Проверка cgroup v2 (нужна для ограничения ресурсов):
stat -fc %T /sys/fs/cgroup/
cgroup2fs
Шаг 2. Установка
Если системный Docker установлен и вы хотите использовать только rootless, отключите системный daemon:
sudo systemctl disable --now docker.service docker.socket
sudo rm -f /var/run/docker.sock
Для параллельной работы этот шаг пропускается.
Запуск установщика без sudo:
dockerd-rootless-setuptool.sh install
Что делает: проверяет предпосылки, создаёт пользовательский unit systemd docker.service, запускает его и печатает переменные окружения, которые нужно установить.
Ожидаемый вывод в конце:
[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger evg`
[INFO] Make sure the following environment variable(s) are set (or add them to ~/.bashrc):
export PATH=/usr/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock
Если скрипт не найден, установите пакет с дополнительными компонентами:
sudo apt install -y docker-ce-rootless-extras
Шаг 3. Настройка окружения
echo 'export DOCKER_HOST=unix:///run/user/'"$(id -u)"'/docker.sock' >> ~/.bashrc
export DOCKER_HOST="unix:///run/user/$(id -u)/docker.sock"
Для оболочки fish файл конфигурации другой (~/.config/fish/config.fish), синтаксис — set -x DOCKER_HOST ....
Шаг 4. Автозапуск
sudo loginctl enable-linger "$USER"
systemctl --user enable docker
Что делает: первая команда разрешает пользовательским службам работать без активной сессии, вторая включает автозапуск daemon.
Шаг 5. Проверка
docker info --format '{{.SecurityOptions}}'
[name=seccomp,profile=builtin name=rootless name=cgroupns]
Наличие name=rootless — подтверждение, что вы работаете с rootless daemon.
Проверка отображения UID:
docker run --rm alpine id
uid=0(root) gid=0(root) groups=0(root),1(bin),...
Внутри container — root. А снаружи, на host:
docker run -d --name uidtest alpine sleep 60
ps -o user,pid,cmd -p "$(docker inspect uidtest --format '{{.State.Pid}}')"
USER PID CMD
evg 184213 sleep 60
Процесс принадлежит вашему пользователю, а не root. Это и есть суть rootless mode.
Уборка:
docker rm -f uidtest
Управление службой
systemctl --user status docker
systemctl --user restart docker
systemctl --user stop docker
Обратите внимание на --user: это пользовательский unit, а не системный. sudo systemctl здесь работать не будет.
Логи:
journalctl --user -u docker -n 50
Переключение между daemon
# rootless
export DOCKER_HOST="unix:///run/user/$(id -u)/docker.sock"
docker info --format '{{.Name}} rootless={{.SecurityOptions}}'
# системный
unset DOCKER_HOST
docker info --format '{{.Name}}'
Удобные функции для ~/.bashrc:
docker-rootless() { export DOCKER_HOST="unix:///run/user/$(id -u)/docker.sock"; echo "rootless"; }
docker-system() { unset DOCKER_HOST; echo "system"; }
Ещё один способ — Docker contexts:
docker context create rootless \
--docker "host=unix:///run/user/$(id -u)/docker.sock"
docker context use rootless
docker context use default
docker context ls
Contexts предпочтительнее переменной окружения: настройка сохраняется между сессиями и видна в docker context ls.
Публикация портов ниже 1024
По умолчанию в rootless mode нельзя опубликовать порт ниже 1024:
docker run -d -p 80:80 nginx
docker: Error response from daemon: driver failed programming external
connectivity on endpoint ...: bind: permission denied
Есть два способа. Оба требуют однократного вмешательства с sudo.
Способ 1: capability для RootlessKit.
sudo setcap cap_net_bind_service=ep "$(which rootlesskit)"
systemctl --user restart docker
Что делает: даёт бинарному файлу rootlesskit право привязываться к привилегированным портам. Ограничено одной программой и одной возможностью.
Способ 2: системная настройка.
echo 'net.ipv4.ip_unprivileged_port_start=0' | \
sudo tee /etc/sysctl.d/99-rootless.conf
sudo sysctl --system
Что делает: разрешает всем непривилегированным процессам системы привязываться к любым портам. Шире по действию, поэтому первый способ предпочтительнее.
Второй способ снимает системное ограничение целиком. На многопользовательской машине это означает, что любой пользователь сможет занять порт 80 или 443. Оценивайте последствия.
Проверка:
docker run -d --name web -p 80:80 nginx
curl -s -o /dev/null -w '%{http_code}\n' http://localhost/
docker rm -f web
200
Ограничение ресурсов
По умолчанию делегируются не все контроллеры cgroup:
cat "/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers"
memory pids
Значит --memory и --pids-limit работают, а --cpus — нет. Чтобы делегировать контроллеры cpu и io:
sudo mkdir -p /etc/systemd/system/user@.service.d
sudo tee /etc/systemd/system/user@.service.d/delegate.conf > /dev/null <<'EOF'
[Service]
Delegate=cpu cpuset io memory pids
EOF
sudo systemctl daemon-reload
Затем перезагрузите систему или переоткройте сессию.
Проверка:
docker run --rm --memory=64m --cpus=0.5 alpine \
sh -c 'cat /sys/fs/cgroup/memory.max'
67108864
67 108 864 байт = 64 MiB. Лимит применился.
Полное удаление Docker
Раздел применим и к обычной, и к rootless установке. Выполняйте шаги по порядку.
Шаг 1. Инвентаризация
Перед удалением. Следующие шаги удаляют образы, containers и volumes безвозвратно. Восстановление возможно только из резервных копий или из registry. Сначала посмотрите, что у вас есть.
docker system df
docker volume ls
docker ps -a
Если среди volumes есть данные, которые нужны, сделайте backup — процедура описана в разделе 07. Образы, которые вы не хотите пересобирать, опубликуйте в registry или сохраните:
docker save -o backup-images.tar image1:tag image2:tag
Шаг 2. Удаление rootless-установки
dockerd-rootless-setuptool.sh uninstall
rootlesskit rm -rf ~/.local/share/docker
Первая команда останавливает и удаляет пользовательский unit. Вторая удаляет каталог данных (через rootlesskit, потому что часть файлов принадлежит подчинённым UID и обычным rm не удаляется).
Отключить lingering, если он больше не нужен:
sudo loginctl disable-linger "$USER"
Шаг 3. Остановка системной службы
sudo systemctl stop docker.socket docker.service containerd.service
sudo systemctl disable docker.socket docker.service
Шаг 4. Удаление пакетов
sudo apt purge -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin \
docker-ce-rootless-extras
sudo apt autoremove -y
purge удаляет и конфигурационные файлы пакетов, в отличие от remove.
Шаг 5. Удаление данных
Опасная команда. Удаляет все образы, containers, volumes, сети и build cache без возможности восстановления. Убедитесь, что шаг 1 выполнен.
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
sudo rm -rf /etc/docker
rm -rf ~/.docker
Что где лежит:
| Путь | Содержимое |
|---|---|
/var/lib/docker | Образы, containers, volumes, сети, build cache |
/var/lib/containerd | Данные containerd (при containerd image store — образы) |
/etc/docker | daemon.json, сертификаты |
~/.docker | Конфигурация CLI, credentials, contexts |
Шаг 6. Удаление репозитория и группы
sudo rm -f /etc/apt/sources.list.d/docker.sources
sudo rm -f /etc/apt/keyrings/docker.asc
sudo apt update
sudo groupdel docker
Шаг 7. Проверка
which docker || echo "docker удалён"
ls /var/lib/docker 2>/dev/null || echo "данные удалены"
getent group docker || echo "группа удалена"
Практическое упражнение
Задание. Настройте rootless Docker параллельно с системным, создайте context для переключения и докажите тремя проверками, что перед вами именно rootless daemon.
Требования к результату:
- Оба daemon работают одновременно.
- Переключение выполняется одной командой (
docker context use). - Доказательства rootless: наличие
name=rootlessв security options, принадлежность процесса container вашему пользователю, отличие пути к каталогу данных.
Подсказки
Подсказка 1
Установка rootless не требует остановки системного daemon, если вы не собираетесь использовать rootless как единственный. Шаг с systemctl disable --now docker пропускается.
Подсказка 2
PID процесса container извлекается так:
docker inspect <name> --format '{{.State.Pid}}'
Владельца процесса покажет ps -o user= -p <pid>.
Подсказка 3
Каталог данных виден в docker info --format '{{.DockerRootDir}}'. Сравните значения для двух contexts.
Решение
Сначала выполните задание самостоятельно.
Показать решение
#!/usr/bin/env bash
# compare-daemons.sh — сравнение системного и rootless daemon.
set -euo pipefail
ROOTLESS_HOST="unix:///run/user/$(id -u)/docker.sock"
# Создать context, если его ещё нет
if ! docker context inspect rootless > /dev/null 2>&1; then
docker context create rootless --docker "host=$ROOTLESS_HOST"
fi
show() {
local ctx="$1"
echo "=== Context: $ctx ==="
docker --context "$ctx" info \
--format ' Security: {{.SecurityOptions}}'
docker --context "$ctx" info \
--format ' Data root: {{.DockerRootDir}}'
docker --context "$ctx" info \
--format ' Storage: {{.Driver}}'
local name="cmp-$ctx-$$"
docker --context "$ctx" run -d --name "$name" alpine sleep 30 > /dev/null
local pid
pid="$(docker --context "$ctx" inspect "$name" --format '{{.State.Pid}}')"
printf ' Владелец процесса container: %s (PID %s)\n' \
"$(ps -o user= -p "$pid" | tr -d ' ')" "$pid"
docker --context "$ctx" rm -f "$name" > /dev/null
echo
}
show default
show rootless
Ожидаемый вывод:
=== Context: default ===
Security: [name=seccomp,profile=builtin name=cgroupns]
Data root: /var/lib/docker
Storage: overlayfs
Владелец процесса container: root (PID 184001)
=== Context: rootless ===
Security: [name=seccomp,profile=builtin name=rootless name=cgroupns]
Data root: /home/пользователь/.local/share/docker
Storage: overlayfs
Владелец процесса container: evg (PID 184213)
Три различия видны сразу: name=rootless в security options, разные каталоги данных, разные владельцы процесса.
Проверка результата
docker context ls
NAME DESCRIPTION DOCKER ENDPOINT
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
rootless unix:///run/user/1000/docker.sock
docker --context rootless info --format '{{.SecurityOptions}}' | grep -o rootless
rootless
Типичные ошибки
| Ошибка | Причина | Исправление |
|---|---|---|
dockerd-rootless-setuptool.sh: command not found | Не установлен пакет с дополнительными компонентами | sudo apt install docker-ce-rootless-extras |
Установщик запущен через sudo | Скрипт должен работать от обычного пользователя | Запустить без sudo |
could not find newuidmap | Не установлен пакет uidmap | sudo apt install uidmap |
/etc/subuid не содержит записи для пользователя | Пользователь создан без выделения диапазонов | sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER |
| Containers останавливаются после выхода из SSH | Не включён lingering | sudo loginctl enable-linger $USER |
docker подключается к системному daemon вместо rootless | Не установлена DOCKER_HOST или не выбран context | docker context use rootless |
--cpus не работает | Контроллер cpu не делегирован пользователю | Настроить Delegate= в drop-in для user@.service |
bind: permission denied для порта 80 | Порты ниже 1024 недоступны по умолчанию | setcap на rootlesskit или net.ipv4.ip_unprivileged_port_start=0 |
| Образы «пропали» после перехода на rootless | У двух daemon разные каталоги данных | Перенести через docker save / docker load |
rm -rf ~/.local/share/docker не удаляет часть файлов | Файлы принадлежат подчинённым UID | rootlesskit rm -rf ~/.local/share/docker |
Контрольные вопросы
На понимание:
- Почему процесс, считающий себя
rootвнутри container, не имеет правrootна host в rootless mode? - Зачем нужны файлы
/etc/subuidи/etc/subgid? - Почему
newuidmapустановлен с битом setuid, и почему это не сводит на нет всю схему? - Почему сеть в rootless mode медленнее?
- Что произойдёт с работающими containers при выходе пользователя из системы без lingering?
На применение:
- Как переключиться между системным и rootless daemon, не редактируя файлы конфигурации?
- Как опубликовать порт 443 в rootless mode, ограничив выданные права одной программой?
- Как проверить, какие cgroup-контроллеры делегированы вашему пользователю?
На диагностику:
docker run --memory=64mв rootless mode выполняется, но лимит не применяется. Где искать причину?- После установки rootless команда
docker imagesпоказывает пустой список, хотя раньше образы были. Что произошло?
Краткое резюме
- В rootless mode daemon и containers работают от обычного пользователя.
- Основа механизма — user namespace, отображающий UID container на подчинённые UID host.
- Диапазоны задаются в
/etc/subuidи/etc/subgid, требуется не менее 65 536 значений. - Установка выполняется через
dockerd-rootless-setuptool.sh installбезsudo. - Rootless daemon слушает
/run/user/<UID>/docker.sockи хранит данные в~/.local/share/docker. - Оба daemon могут работать параллельно; переключение — через Docker contexts.
loginctl enable-lingerнужен, чтобы containers пережили выход из сессии.- Ограничения: AppArmor, checkpoint, overlay network, SCTP-порты не поддерживаются; сеть работает в пространстве пользователя.
- Ограничение ресурсов требует cgroup v2 с systemd; по умолчанию делегируются только
memoryиpids. - Порты ниже 1024 требуют
setcapнаrootlesskitлибо системной настройкиip_unprivileged_port_start.
Официальные источники
| Источник | Ссылка | Что подтверждает |
|---|---|---|
| Rootless mode | https://docs.docker.com/engine/security/rootless/ | Предпосылки, установка через dockerd-rootless-setuptool.sh, DOCKER_HOST, loginctl enable-linger, список известных ограничений, публикация привилегированных портов, требования к cgroup v2 |
| Rootless mode: tips | https://docs.docker.com/engine/security/rootless/tips/ | Делегирование cgroup-контроллеров, дополнительные настройки |
| Rootless mode: troubleshooting | https://docs.docker.com/engine/security/rootless/troubleshoot/ | Диагностика типичных проблем установки |
| Docker contexts | https://docs.docker.com/engine/manage-resources/contexts/ | Создание и переключение contexts |
| Isolate containers with a user namespace | https://docs.docker.com/engine/security/userns-remap/ | Альтернативный механизм userns-remap |
subuid(5) man page | https://man7.org/linux/man-pages/man5/subuid.5.html | Формат файлов подчинённых UID |
user_namespaces(7) man page | https://man7.org/linux/man-pages/man7/user_namespaces.7.html | Механизм user namespaces в ядре |
Навигация
← Предыдущий материал
Вернуться к разделу
Следующий материал → Практические задания
Главное оглавление