Що таке rootless Docker і коли його використовувати?
Rootless Docker крутить увесь Docker-стек як unprivileged-user. Daemon не потребує root; container теж. Це найсильніше security-hardening, що можна застосувати до Docker-інсталяції, з певними операційними trade-off.
Теорія
TL;DR
- Стандартний Docker:
dockerdкрутиться як root. Container-процеси обмежені, але сам daemon privileged. - Rootless Docker:
dockerdкрутиться як звичайний user через Linux user-namespaces. Увесь стек unprivileged на host. - Container-escape з rootless Docker = компроміс user, не host-root. Велика різниця для shared-інфраструктури.
- Trade-off: без
--privileged-container, без bind на порти нижче 1024 за замовчуванням, повільніша мережа через slirp4netns, ~20% storage I/O overhead, безdocker run --network hostдля host. - Бери коли: security-чутливі multi-tenant host, CI-runner, HPC-кластери, регульовані середовища. Скіпай коли: потрібні privileged-container, дуже високі I/O навантаження, kernel-модулі.
Як це працює
Rootless Docker використовує user-namespaces, щоб remap UID:
Host: Всередині rootless-container:
user 'me' (UID 1000) root (UID 0)
↑ ↓
+- крутить dockerd +- всередині цього user-namespace,
що використовує subuid 100000-165535 'root' це local-конструкт,
↓ що мапиться на host UID 100000
+- container крутяться як +- escape дає доступ до
UID 100000-165535 UID 100000, НЕ host rootKernel user-namespace дає кожному container свій UID 0 (root всередині), що мапиться на інший unprivileged UID зовні. Daemon крутиться як звичайний user (UID 0 на host не потрібен).
Інсталяція rootless Docker
# Інсталяція (не потребує sudo для самої rootless-інсталяції)
curl -fsSL https://get.docker.com/rootless | sh
# Інсталер друкує інструкції; ключові частини:
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
# Стартуємо daemon
systemctl --user start docker
# (або `dockerd-rootless.sh &` напряму)
docker run --rm hello-worldDOCKER_HOST env-змінна показує на user-daemon socket, окремий від будь-якого системного Docker.
Trade-off
Що rootless віддає
# Не може bind на low-порти без setcap (system-wide)
docker run -p 80:80 nginx # "permission denied" за замовчуванням
# Обхід: бери вищий порт (8080) або грант CAP_NET_BIND_SERVICE
# Без --privileged або capability-додавання понад user-ліміти
docker run --privileged ... # падає
# Повільніша мережа (slirp4netns userland TCP/IP стек)
# Throughput: ~половина від bridge networking
# Повільніший storage (overlay2 потребує спецналаштування; часто fallback на fuse-overlayfs)
# I/O: ~20% повільніше за rootful
# Без system-сервісів / без host-мережі
docker run --network host ... # падаєБільшість user-space навантажень (веб-сервери на високих портах, app-container, мовні runtime) працюють нормально. Privileged або kernel-level робота ламається.
Що rootless дає
- Без root-daemon для атаки. Найбільша поверхня privilege-escalation у класичному Docker зникає.
- Per-user ізоляція. Два user на тому самому host крутять окремі Docker-стеки; один не бачить container іншого.
- Без
docker-group ризику. Класичне попередження «додай мене у docker-group = root» не застосовується; rootless-daemon прив'язаний до user. - Простіша audit-історія. «Що може цей Docker?» відповідь: «що завгодно, що може його user».
Порівняння з Podman
Podman daemonless і rootless за замовчуванням. Два рішення перетинаються:
| Rootless Docker | Podman | |
|---|---|---|
| Daemon | так (per-user dockerd) | ні (кожен podman свій процес) |
| Rootless | так (opt-in інсталяція) | так (дефолт) |
| Compose-підтримка | так (docker compose) | так (podman compose) |
| Image-формат | OCI | OCI |
| Зрілість | зріла | зріла |
| Linux distro дефолти | Docker de-facto стандарт | Podman дефолт на RHEL/Fedora |
Для security-first деплоїв на Red Hat-based системах Podman це шлях найменшого опору. Для змішаної Docker/legacy-сумісності, rootless Docker.
Налаштування port-bindings нижче 1024
# Грант capability rootlesskit-бінару bind на low-порти
sudo setcap cap_net_bind_service=ep $(which rootlesskit)
# Або per-container, менш поширеноЦе найпоширеніша operational-перешкода. Як налаштовано, -p 80:80 працює.
Типові помилки
Міксувати rootless і rootful daemon
# Обидва можуть крутитися
sudo systemctl status docker # system-wide rootful
systemctl --user status docker # user-level rootlessЯкщо обидва крутяться, docker-команди йдуть на той, на що вказує DOCKER_HOST. Плутає. Обери один на машину.
Спроба --privileged і отримати unhelpful-помилки
$ docker run --privileged ubuntu mount
mount: /proc/sys: must be mounted on /proc/sysRootless не може грантити --privileged. Якщо застосунок це потребує, rootless не для тебе.
Забути, що мережа повільніша
Для більшості app slirp4netns нормально (типовий веб-трафік). Для high-throughput сервісів бенчмарк перед commit.
Filesystem permission-сюрпризи
$ docker run -v /home/me/data:/data myapp
# Всередині: файли як власник 'nobody' або дивні UIDUID-mapping rootless-daemon впливає на file-ownership. -v bind mount може показати незвичний ownership всередині container. Плануй UID відповідно.
Реальне застосування
- CI runners: GitHub Actions self-hosted runner часто беруть rootless Docker, кілька user на одному host, без escape у host root.
- HPC-кластери: scientific computing на shared-node; rootless дає кожному user свій Docker без sysadmin-grant.
- Per-tenant container-host: multi-tenant сервери, де кожен tenant крутить свій dockerd unprivileged.
- Регульовані середовища: фінанси, defense, healthcare, аудитори обожнюють «без root-daemon».
- Devel-лептопи на cooperative-машинах: якщо лептоп shared (рідко зараз, але буває), rootless запобігає, щоб container одного user заволодів host.
Питання для поглиблення
Q: Чи rootless Docker працює на macOS / Windows?
A: Docker Desktop на Mac/Windows уже крутить daemon всередині Linux VM, ізолюючи від host-OS. Функціонально схожа security з rootless на Linux. Термінологія rootless для Linux-host сценаріїв.
Q: Чи rootless Docker production-ready?
A: Так, з ~2020. Використовується на масштабі GitHub, Red Hat (через Podman), різними HPC-сайтами. Достатньо зрілий, щоб бути дефолтом для security-conscious деплоїв.
Q: Що таке slirp4netns?
A: Userspace TCP/IP-стек, що дає unprivileged-container мережеве з'єднання. Повільніше за kernel-networking (бо реалізує TCP/IP у userspace), але не потребує root. Є також vpnkit (Docker Desktop) і pasta (новіший, швидший).
Q: Чи можу крутити rootless Docker поряд з rootful Docker на тому самому host?
A: Так, але вони окремі. Різні socket (/var/run/docker.sock vs $XDG_RUNTIME_DIR/docker.sock), різні container, різні мережі. DOCKER_HOST обирає, з ким CLI говорить.
Q: (Senior) Коли rootless trade-off НЕ має сенсу?
A: Коли навантаження вимагає kernel-фіч, що rootless не може дати: device pass-through, kernel-module loading, low-level network-маніпуляції (raw sockets, eBPF), реальний --privileged для nested-virtualization або extremely I/O-bound навантажень, де 20% overhead неприйнятний. Для цього прийми rootful Docker і hard'ни його іншими засобами (seccomp, AppArmor, user-namespace remap на daemon-рівні через userns-remap).
Приклади
Side-by-side: той самий docker run, інша security-модель
# Rootful Docker (дефолт)
sudo systemctl start docker
docker run --rm alpine ps -ef
# UID 0 всередині, dockerd як root на host, container-escape = host root
# Rootless Docker
systemctl --user start docker
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
docker run --rm alpine ps -ef
# UID 0 всередині, dockerd як user на host, container-escape = unprivileged userТа сама команда, дуже різний радіус ураження.
Налаштування CI-runner
# Як runner-user
curl -fsSL https://get.docker.com/rootless | sh
# Persist env
cat >> ~/.bashrc <<'EOF'
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
EOF
# Auto-start при login (lingering тримає сервіс між SSH-сесіями)
sudo loginctl enable-linger runner
systemctl --user enable docker
systemctl --user start dockerТепер кожен runner-екземпляр має свій Docker-стек, без shared root-daemon.
Верифікація rootless
$ docker info | grep -i 'rootless\|cgroup'
rootless
Cgroup Driver: cgroupfs
$ ps -ef | grep dockerd
runner 12345 1 /home/runner/bin/dockerd-rootless.sh
# Зверни увагу: НЕ як root
$ docker run --rm alpine cat /proc/self/status | grep CapEff
CapEff: 0000003fffffffff
# Capabilities все одно обмежені user-allowanceDaemon-процес належить runner, не root. Container-capabilities обмежено host-capabilities user.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.
Коментарі
Ще немає коментарів