Gymterview
junior

Как запустить контейнер не от пользователя root?

Запуск контейнера не от пользователя root – это практика создания и использования непривилегированного пользователя (с UID отличным от 0) для выполнения процесса внутри контейнера, что предотвращает получение атакующим root-прав на хосте при побеге из контейнера.

Инструкция USER в Dockerfile

Основной способ – создать непривилегированного пользователя и переключиться на него:

Пример Dockerfile с непривилегированным пользователем
FROM eclipse-temurin:21-jre-alpine

# Создаём группу и пользователя
RUN addgroup -S javaapp && adduser -S javaapp -G javaapp

# Создаём директории и назначаем владельца
RUN mkdir -p /app/logs /app/config && \
    chown -R javaapp:javaapp /app

WORKDIR /app

# Копируем артефакт
COPY --chown=javaapp:javaapp target/banking-service.jar /app/app.jar

# Переключаемся на непривилегированного пользователя
USER javaapp

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Директива runAsNonRoot в Kubernetes

Kubernetes может принудительно запретить запуск контейнеров от root:

Пример Deployment с runAsNonRoot
apiVersion: apps/v1
kind: Deployment
metadata:
  name: banking-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: banking-service
  template:
    metadata:
      labels:
        app: banking-service
    spec:
      securityContext:
        runAsNonRoot: true
        fsGroup: 1000
      containers:
      - name: app
        image: registry.bank.local/banking-service:1.0.0
        securityContext:
          runAsUser: 1000
          runAsGroup: 1000
          allowPrivilegeEscalation: false

Если в образе USER указан root, а в Kubernetes выставлен runAsNonRoot: true, кластер откажется запустить под и выдаст ошибку CreateContainerConfigError.

User namespace remapping в Docker daemon

Этот механизм сопоставляет UID 0 внутри контейнера с непривилегированным UID на хосте:

Пример
// /etc/docker/daemon.json
{
  "userns-remap": "default"
}

Даже если процесс внутри контейнера работает как root (UID 0), на хосте он будет обычным пользователем (например, UID 100000). Это обеспечивает дополнительный уровень защиты при побеге из контейнера.

Типичные проблемы при запуске не от root

Проблема Решение
Нет прав на запись в /app/logs RUN mkdir /app/logs && chown appuser:appgroup /app/logs
Порт ниже 1024 (например, 80) Использовать порт 8080+ или добавить NET_BIND_SERVICE capability
Java не может создать временные файлы Примонтировать tmpfs или emptyDir в /tmp
Не удаётся подключиться к БД через unix-socket Настроить права на сокет или использовать TCP

Проверка текущего пользователя

Пример
docker exec my-container whoami
docker exec my-container id
# uid=1000(javaapp) gid=1000(javaapp) groups=1000(javaapp)

Вывод

Запуск от непривилегированного пользователя – первая и простейшая мера защиты контейнера. Комбинация инструкции USER в Dockerfile и runAsNonRoot: true в Kubernetes гарантирует, что процесс никогда не запустится от root, даже если кто-то случайно соберёт образ без USER.

На собеседовании: достаточно показать знание трёх уровней защиты: USER в Dockerfile, runAsNonRoot в Kubernetes, и user namespace remapping как дополнительный барьер. Также упомяните типичные проблемы – это демонстрирует практический опыт.