Короткий ответ

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

Возможные причины

  1. Копипаста docker run из блога «fix permission».
  2. Нужен был ping (NET_RAW), дали всё.
  3. Вложенные контейнеры / kind / CI dind — тогда смотрите sock, не privileged на бизнес-сервис.
  4. Старый обход AppArmor.
  5. Сборщик пакетов внутри контейнера с mount /dev.
  6. Редко: 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:
      - /tmp

cap_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/health

Privileged 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 навсегда без разбора.