Docker: как запускать контейнер не от root и снизить риски

По умолчанию процесс в контейнере работает от root. Побег из контейнера через уязвимость тогда даёт права root на хосте. Ниже меры, которые уменьшают этот риск и занимают несколько строк в Dockerfile и в запуске.
Шаг 1. Пользователь без прав в образе
FROM python:3.12-slim
RUN useradd --system --uid 10001 --no-create-home app
WORKDIR /app
COPY --chown=app:app . .
USER app
CMD ["python", "main.py"]
Если образ чужой, задайте пользователя при запуске: docker run --user 10001:10001 .... Некоторым приложениям нужны права на запись в каталоги, подготовьте их заранее.
Шаг 2. Уберите лишние привилегии при запуске
docker run -d --name app \
--read-only --tmpfs /tmp \
--cap-drop ALL \
--security-opt no-new-privileges:true \
--memory 512m --pids-limit 200 \
myapp:1.4.2
--read-onlyзапрещает запись в файловую систему контейнера, запись разрешена только в/tmpи в смонтированные тома.--cap-drop ALLубирает системные возможности. Верните только нужные через--cap-add.no-new-privilegesне позволяет повысить права через setuid-файлы.
Если приложение не стартует, смотрите docker logs и добавляйте разрешения по одному, а не возвращайте всё сразу.
Шаг 3. Не используйте опасные режимы
--privilegedдаёт контейнеру почти полный доступ к хосту.- Монтирование
/var/run/docker.sockвнутрь контейнера равносильно root на хосте: кто управляет сокетом, тот запускает любые контейнеры. --network hostубирает сетевую изоляцию.
Шаг 4. Порты привязывайте к нужному адресу
docker run -p 127.0.0.1:5432:5432 postgres:15-alpine # только с самого хоста
docker run -p 5432:5432 postgres:15-alpine # доступно всему интернету
Docker сам вносит правила в firewall и обходит ufw, поэтому опубликованный порт может быть открыт, даже если вы его «закрыли». Подробнее о firewall: nftables и firewalld. В Compose используйте тот же формат: "127.0.0.1:5432:5432".
Шаг 5. Образы и обновления
- Берите образы из проверенных источников и закрепляйте версию тегом, а не
latest. - Проверяйте образы на известные уязвимости:
trivy image myapp:1.4.2. - Не кладите секреты в образ и в переменные окружения в Dockerfile: секреты в git.
Как проверить
docker exec app id
docker inspect app --format 'user={{.Config.User}} priv={{.HostConfig.Privileged}} ro={{.HostConfig.ReadonlyRootfs}} caps={{.HostConfig.CapDrop}}'
docker ps --format '{{.Names}} {{.Ports}}'
docker ps -q | xargs docker inspect --format '{{.Name}} priv={{.HostConfig.Privileged}}'
Команда id должна показывать не uid=0, privileged — false. Список портов покажет контейнеры, открытые на 0.0.0.0. Эти порты проверьте снаружи сканером: Nmap. Для более строгой изоляции изучите rootless-режим Docker по документации.
















