Skip to main content

Що таке 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 root

Kernel user-namespace дає кожному container свій UID 0 (root всередині), що мапиться на інший unprivileged UID зовні. Daemon крутиться як звичайний user (UID 0 на host не потрібен).

Інсталяція rootless Docker

bash
# Інсталяція (не потребує 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-world

DOCKER_HOST env-змінна показує на user-daemon socket, окремий від будь-якого системного Docker.

Trade-off

Що rootless віддає

bash
# Не може 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 DockerPodman
Daemonтак (per-user dockerd)ні (кожен podman свій процес)
Rootlessтак (opt-in інсталяція)так (дефолт)
Compose-підтримкатак (docker compose)так (podman compose)
Image-форматOCIOCI
Зрілістьзрілазріла
Linux distro дефолтиDocker de-facto стандартPodman дефолт на RHEL/Fedora

Для security-first деплоїв на Red Hat-based системах Podman це шлях найменшого опору. Для змішаної Docker/legacy-сумісності, rootless Docker.

Налаштування port-bindings нижче 1024

bash
# Грант capability rootlesskit-бінару bind на low-порти sudo setcap cap_net_bind_service=ep $(which rootlesskit) # Або per-container, менш поширено

Це найпоширеніша operational-перешкода. Як налаштовано, -p 80:80 працює.

Типові помилки

Міксувати rootless і rootful daemon

bash
# Обидва можуть крутитися sudo systemctl status docker # system-wide rootful systemctl --user status docker # user-level rootless

Якщо обидва крутяться, docker-команди йдуть на той, на що вказує DOCKER_HOST. Плутає. Обери один на машину.

Спроба --privileged і отримати unhelpful-помилки

bash
$ docker run --privileged ubuntu mount mount: /proc/sys: must be mounted on /proc/sys

Rootless не може грантити --privileged. Якщо застосунок це потребує, rootless не для тебе.

Забути, що мережа повільніша

Для більшості app slirp4netns нормально (типовий веб-трафік). Для high-throughput сервісів бенчмарк перед commit.

Filesystem permission-сюрпризи

bash
$ docker run -v /home/me/data:/data myapp # Всередині: файли як власник 'nobody' або дивні UID

UID-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-модель

bash
# 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

bash
# Як 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

bash
$ 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-allowance

Daemon-процес належить runner, не root. Container-capabilities обмежено host-capabilities user.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Коментарі

Ще немає коментарів