Короткий ответ
privileged: true почти снимает изоляцию: все capabilities, почти все устройства, лёгкий путь к хосту. На host.example для проекта app это инцидент, не «чтобы GPU/ping заработал». Снимите флаг, верните cap_drop: [ALL] и явный cap_add (часто NET_BIND_SERVICE, иногда NET_RAW для ping). Не заменяйте privileged пробросом docker.sock. Не отключайте AppArmor unconfined взамен.
Симптомы и как отличить
docker inspect app-web-1 --format '{{.HostConfig.Privileged}}'→true;- внутри видны диски хоста в
/dev; - compose YAML
privileged: trueилиcap_add: [SYS_ADMIN, SYS_MODULE]; - «иначе не монтируется FUSE/NFS» — часто решаемо без privileged.
| Режим | Оценка |
|---|---|
| privileged | недопустимо для app |
| cap_add SYS_ADMIN | почти так же плохо, разбор отдельно |
| cap_add NET_BIND_SERVICE | норма для :80 без root иногда |
| sock mount | хуже или равно privileged |
Возможные причины
- Копипаста docker run из блога «fix permission».
- Нужен был ping (
NET_RAW), дали всё. - Вложенные контейнеры / kind / CI dind — тогда смотрите sock, не privileged на бизнес-сервис.
- Старый обход AppArmor.
- Сборщик пакетов внутри контейнера с mount /dev.
- Редко: realtime устройства без DeviceAllow.
Диагностика
cd /opt/app
grep -n -E 'privileged|cap_add|cap_drop|security_opt' compose.yaml
docker inspect app-web-1 --format 'priv={{.HostConfig.Privileged}}
caps={{json .HostConfig.CapAdd}}
drop={{json .HostConfig.CapDrop}}
apparmor={{index .AppArmorProfile}}
seccomp={{.HostConfig.SecurityOpt}}'
docker compose exec web sh -c 'cat /proc/1/status | grep Cap; ls /dev | head'CapEff полный набор — подтверждение. Сравните с не-privileged контейнером.
Документируйте зачем флаг стоял (тикет), иначе вернёте через неделю.
Решение
Сценарий A. Обычный web/db — просто снять
services:
web:
privileged: false
cap_drop:
- ALL
cap_add: []
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmpcap_add: [] или секция только drop. Recreate:
docker compose up -d --force-recreate web
docker inspect app-web-1 --format '{{.HostConfig.Privileged}}'Volume app_data остаётся. Если приложение упало с EPERM — добавьте одну capability, не privileged обратно.
Сценарий B. Нужен bind :80 внутри контейнера
Лучше слушать 8080 + publish. Если всё же:
cap_drop: [ALL]
cap_add: [NET_BIND_SERVICE]И non-root user.
Сценарий C. ping/диагностика сети
cap_add: [NET_RAW]Или не используйте ping, используйте curl. Не SYS_ADMIN.
Сценарий D. FUSE / docker-in-docker для CI
Бизнес-сервис app туда не относится. Вынесите dind на отдельный runner с изоляцией, не на прод-хост с app_data. SYS_ADMIN + /dev/fuse лучше privileged, всё равно высокий риск — не прод.
Сценарий E. Устройства GPU
device_requests / NVIDIA CDI, не privileged. Документация вендора Engine 27, не gist 2016 года.
Проверка регрессии приложения — обязательна: логи, healthcheck.
Privileged отключает почти все ограничения seccomp, apparmor (часто профиль unconfined), device cgroup. Контейнер видит /dev хоста, может mount, загрузить модуль ядра при наличии SYS_MODULE, переписать хостовые диски. Для сервиса web проекта app это никогда не требование протокола HTTP. Типичные «причины» из тикетов: ping, chronyc, docker-in-docker, «иначе permission denied на volume». Последнее лечится UID, не privileged.
После снятия флага снимите отпечаток: capsh --print внутри, ls /dev, cat /proc/1/status поля CapEff/CapPrm. Сравните с эталоном не-privileged nginx. Если приложение требует /dev/net/tun, это devices: или cap_add: NET_ADMIN на отдельном VPN-контейнере, не на web с app_data. Не копируйте тот же YAML на все сервисы.
Seccomp default 27.x режет редкие syscall (iopl, некоторые bpf). Сообщение Operation not permitted без errno в логе приложения часто отсюда. Снимите strace -f на короткий старт в окне, найдите syscall, добавьте профиль или уберите вызов в коде. seccomp=unconfined — почти privileged по части syscall. Документируйте исключение с датой.
Не оставляйте privileged: true в override-файле compose.override.yaml, который не в git: прод и git будут расходиться, baseline снова красный. docker compose config должен показать false.
Как проверить, что проблема устранена
docker inspect app-web-1 --format '{{.HostConfig.Privileged}} {{json .HostConfig.CapAdd}} {{json .HostConfig.CapDrop}}'
docker compose exec web sh -c 'ls /dev/sda 2>/dev/null && echo BAD_HOST_DISK || echo no_host_disk'
curl -sS -o /dev/null -w '%{http_code}\n' --max-time 5 http://127.0.0.1:8080/healthPrivileged false. Нет дисков хоста. Сервис отвечает. AppArmor профиль не unconfined, если был docker-default.
Если не помогло
- EPERM на syslog/time: не cap SYS_TIME в проде без нужды; чините код.
- Permission на volume — UID, не privileged: права volume.
- Старый systemd unit
docker run --privilegedв обход compose — найдите unit. - Оркестратор вернул privileged из Helm values — чините манифест, не только YAML на диске.
На хостах с NVIDIA CDI привилегии устройства задаются device_requests, не privileged. Если вендорский скрипт всё ещё требует privileged, изолируйте GPU-сервис от app_data и сети db. Не распространяйте тот же флаг на web. После выкладки сохраните docker inspect Privileged=false в артефакт заявки.
Профилактика
- CI grep
privileged:true. - Политика: cap_drop ALL по умолчанию.
- Review на SYS_ADMIN/SYS_MODULE.
- Baseline hardening.
- Не давать разработчикам docker.sock на этом хосте — иначе вернут флаг сами.
Проверка регрессии после снятия privileged: не только HTTP 200, но запись в app_data, DNS db, healthcheck. Часто всплывает EPERM на chown в entrypoint — уберите chown из старта, сделайте его разово с хоста. Всплывает bind /sys — он не нужен web. Всплывает docker.sock «как замена privileged» — это хуже, см. соседнюю статью. Зафиксируйте docker compose config в git-артефакт выкладки, чтобы флаг не вернулся через override.
FAQ
Чем privileged отличается от cap_add ALL?
Privileged ещё ослабляет cgroup/device/seccomp. cap_add ALL уже плох, privileged хуже.
Нужен ли privileged для clock?
Нет. Хост NTP. Контейменту не ставить время.
Docker Desktop «privileged for kube»
Не переносите на Ubuntu Engine с бухгалтерией.
Можно ли оставить на redis «маленький privileged»?
Нет.
После снятия не стартует: Operation not permitted mmap
Иногда seccomp. Профиль по умолчанию режет редкий syscall. security_opt: ["seccomp=...json"] точечно, не privileged и не seccomp=unconfined навсегда без разбора.