Ответ
Для смены пользователя в Dockerfile используется инструкция USER. Это критически важно для безопасности, чтобы контейнер не работал от root без необходимости.
Базовый пример:
FROM alpine:3.18
RUN addgroup -g 1000 appgroup &&
adduser -u 1000 -G appgroup -D appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "index.js"]
Практические сценарии:
-
Снижение привилегий после установки пакетов:
FROM ubuntu:22.04 RUN apt-get update && apt-get install -y python3 && rm -rf /var/lib/apt/lists/* RUN useradd -m -u 1001 apprunner USER apprunner -
Использование UID вместо имени (рекомендуется для Kubernetes):
USER 1001 -
Многоступенчатая сборка со сменой пользователя:
FROM golang:1.21 AS builder WORKDIR /build COPY . . RUN CGO_ENABLED=0 go build -o app .
FROM scratch COPY --from=builder /build/app /app USER 1000:1000 ENTRYPOINT ["/app"]
**Важные моменты:**
- Пользователь должен существовать (создан через `RUN useradd/adduser`)
- `USER` влияет на все последующие `RUN`, `CMD`, `ENTRYPOINT`
- В Kubernetes SecurityContext может переопределять пользователя контейнера
- Для доступа к привилегированным портам (<1024) всё равно потребуется root или `NET_BIND_SERVICE` capability Ответ 18+ 🔞
Э, слушай, вот история про безопасность в докере, которая многих накрывает медным тазом. Все лепят образы от рута, а потом охуевают, когда какой-нибудь хитрожопый эксплойт выгребает из контейнера всё, включая соседские сервисы. Так делать — это пиздец какой риск, доверия ебать ноль.
Короче, чтобы не быть распиздяем, в Dockerfile есть инструкция USER. Её задача — переключить того, кто внутри контейнера команды выполняет, с всесильного root на какого-нибудь смертного юзера. Без этого твой контейнер — это как дом с открытой дверью и табличкой «заходите, всё ваше».
Самый простой пример, с чего начать:
FROM alpine:3.18
RUN addgroup -g 1000 appgroup &&
adduser -u 1000 -G appgroup -D appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
USER appuser
CMD ["node", "index.js"]
Видишь? Сначала создали группу и юзера, скопировали файлы, а потом уже сказали USER appuser. Всё, что после — CMD, ENTRYINT — будет крутиться от его имени. Уже не так страшно.
А вот как это в жизни применяется, смотри:
-
Сделал грязную работу — смени перчатки. Установил пакеты от рута, а потом переключился на простого работягу.
FROM ubuntu:22.04 RUN apt-get update && apt-get install -y python3 && rm -rf /var/lib/apt/lists/* # Почистил за собой, молодец RUN useradd -m -u 1001 apprunner USER apprunner # И пошёл работать под непривилегированным юзеромЛогика простая: всё, что требует прав — делаем в начале, а потом опускаемся до уровня обычного пользователя. Красота.
-
Для кубера — только цифры. Там своя банда, свои понятия. Им часто похуй на имена, они работают с UID напрямую. Поэтому просто:
USER 1001Коротко и ясно. Главное, чтобы этот UID (и GID) в итоговом образе реально существовали, а то будет волнение ебать при старте.
-
Многоступенчатая сборка — высший пилотаж. Собираем в одном образе, а живём в другом, максимально стерильном.
FROM golang:1.21 AS builder WORKDIR /build COPY . . RUN CGO_ENABLED=0 go build -o app . FROM scratch # Вообще пустой образ, ёпта! COPY --from=builder /build/app /app USER 1000:1000 # И сразу говорим, под кем работать ENTRYPOINT ["/app"]Вот это подход! Из
scratchдаже рута нет, только твой бинарь и указанный UID/GID. Безопасность почти абсолютная.
Важные моменты, которые в голове держать надо:
- Сначала создай, потом используй. Нельзя написать
USER вася, если этого Васи в образе нет. СначалаRUN useradd..., потом ужеUSER. Иначе будет тебе хиросима на старте. USER— это надолго. Всё, что идёт после этой инструкции (RUN,CMD,ENTRYPOINT), будет выполняться от указанного пользователя. Это навсегда (пока следующийUSERне перебил).- Кубер может всё переопределить. Не обольщайся. Если в поде в
SecurityContextпрописанrunAsUser: 0, то твой красивыйUSER 1001нахуй не сдался. Кубер его перетрет. Но лучше, чтобы в образе было не от рута по умолчанию. - Порты ниже 1024 — привилегия рута. Если твоему сервису надо висеть на 80-м порту, просто так под обычным юзером не выйдет. Варианты: слушать на высоком порту (типа 8080), использовать
NET_BIND_SERVICEcapability (это уже тонкости), или всё-таки стартовать от рута (но это крайний случай, и тогда терпения ноль ебать к его безопасности).
Короче, суть в чём: не будь пидарасом шерстяным, не ленись прописывать USER. Это не просто «хорошая практика», это базовый способ не наебаться с безопасностью. Делай с умом.