Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке rootless Docker і коли його використовувати?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Rootless Docker** крутить `dockerd` як non-root-user через Linux user-namespaces. Daemon, container, усе крутиться unprivileged на host. Container-escape дає attacker permissions unprivileged-user, не host-root. ```bash curl -fsSL https://get.docker.com/rootless | sh export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock docker run --rm hello-world ``` **Головне:** великий security-win, без root-daemon. Trade-off: без privileged-container, повільніша мережа (slirp4netns), без system-сервісів, ~20% I/O overhead. Бери для security-чутливих multi-tenant host, HPC-кластерів, CI-runner; не там, де потрібні повні Docker-capabilities.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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 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 ```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.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.