Что указывается в директиве USER в Dockerfile?

Ответ

Директива USER в Dockerfile задаёт имя пользователя (или его UID) и опционально группу (или GID), от имени которого будут запускаться все последующие инструкции RUN, CMD и ENTRYPOINT в этом образе.

Форматы указания:

  • USER <user> (например, USER app)
  • USER <uid> (например, USER 1000)
  • USER <user>:<group> (например, USER app:app)
  • USER <uid>:<gid> (например, USER 1000:1000)

Пример в Dockerfile:

FROM alpine:latest
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Копируем файлы от root
COPY --chown=appuser:appgroup app.jar /opt/app/
# Переключаем пользователя
USER appuser
WORKDIR /opt/app
CMD ["java", "-jar", "app.jar"]

Зачем это нужно с точки зрения DevOps и безопасности:

  1. Снижение привилегий (Principle of Least Privilege): Запуск контейнера от root (по умолчанию, если USER не указан) даёт процессу внутри контейнера неограниченные права. Если злоумышленник скомпрометирует такое приложение, он получит root-доступ к хостовой системе (особенно при использовании --privileged флага или уязвимостей в ядре). Запуск от непривилегированного пользователя минимизирует этот риск.
  2. Соответствие стандартам безопасности: Требования многих compliance-стандартов (например, PCI DSS, CIS Benchmarks) прямо запрещают запуск контейнеров от root.
  3. Изоляция от хоста: Использование несуществующего на хосте высокого UID (например, USER 10000:10000) может добавить дополнительный уровень изоляции.

Важные замечания:

  • Указанный пользователь или UID/GID должны существовать внутри образа. Их нужно создать с помощью RUN (как в примере выше) или использовать базовый образ, где они уже есть.
  • Директива USER влияет только на инструкции, следующие за ней в Dockerfile. Инструкции до USER выполняются от root (или от пользователя, заданного предыдущей директивой USER).
  • Пользователя можно переопределить при запуске контейнера флагом docker run -u <user>.

Ответ 18+ 🔞

А, слушай, вот про USER в Dockerfile надо бы объяснить, а то народ иногда как об стенку горохом — вроде слышат, а нихуя не вникают. Это ж, бля, основа безопасности, а не просто строчка для галочки.

Представь себе, что твой контейнер — это квартира. По умолчанию, если ты не указал USER, то процесс внутри бегает как полноправный хозяин-рут. У него ключи от всех замков, доступ к сейфу и право менять планировку. А теперь представь, что в этой квартире живёт твое приложение, и в нём нашли дыру. Злоумышленник через эту дыру залезает внутрь и — охуенно! — он теперь тоже рут в этом контейнере. А если на хосте не всё настроено идеально (да хуй там, никогда не бывает идеально), то он может с этого рут-аккаунта в контейнере попробовать пролезть на сам хост. Вот тебе и хиросима, как говорится.

А теперь суть: директива USER — это ты говоришь: «Так, дружок-приложение, ты будешь жить в этой квартире, но не как хозяин, а как гость. Вот тебе тапочки, вот диван, хуярь тут своё приложение, но до сейфа и перегородок тебе дела нет». Запускаешь процесс от простого, непривилегированного юзера. Даже если его скомпрометируют — ущерб будет локальным, ограниченным. Доверия ебать ноль должно быть по умолчанию, это золотое правило.

Как это выглядит в деле, смотри:

FROM alpine:latest
# Сначала от рута создаём юзера и группу
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Копируем файлы, заодно меняем владельца на нашего юзера
COPY --chown=appuser:appgroup my-app.jar /opt/app/
# И вот он, магический момент — переключаемся
USER appuser
# Всё, что ниже, выполняется уже от appuser
WORKDIR /opt/app
CMD ["java", "-jar", "my-app.jar"]

Форматы указания — хуй с горы, всё просто:

  • USER appuser
  • USER 1000
  • USER appuser:appgroup
  • USER 1000:1000

Главное, чтобы этот пользователь или UID/GID реально существовали в образе! А то будет тебе удивление пиздец, когда контейнер на старте накроется медным тазом с ошибкой, что юзер не найден. Сначала создай (RUN adduser...), потом используй.

С точки зрения параноика-девопса, есть ещё хитрая жопа: можно использовать высокий UID, который заведомо не существует на хостовой системе, например, USER 10000:10000. Это как дополнительный замок на дверь — даже если кто-то вырвется, сопоставить его с реальным пользователем на хосте будет сложнее.

И да, запомни: USER в Dockerfile — это дефолт. Его можно взъебнуть при запуске через docker run -u 1234. Но если в Dockerfile его нет — ты по умолчанию летишь от рута, а это, считай, пидарас шерстяной в плане безопасности.

Короче, резюме: хуй моржовый запускать от root в продакшене. Создал юзера, скопировал файлы с --chown, переключился через USER и спи спокойно. Безопасность — она не в одном громком слове, а в таких вот простых, но обязательных действиях. Э бошка думай, когда пишешь Dockerfile.