Files
docker_practice/04_image/dockerfile/user.md

5.7 KiB
Raw Blame History

USER 指定当前用户

基本语法

USER <用户名>[:<用户组>]
USER <UID>[:<GID>]

USER 指令切换后续指令RUN、CMD、ENTRYPOINT的执行用户。


为什么要使用 USER

笔者强调:以非 root 用户运行容器是最重要的安全实践之一。

root 用户运行的风险:
┌────────────────────────────────────────────────────────┐
│  容器内 root  ←─ 可能逃逸 ─→  宿主机 root             │
│      │                            │                    │
│      └── 漏洞利用 ───────────────→ 完全控制宿主机     │
└────────────────────────────────────────────────────────┘

非 root 用户运行:
┌────────────────────────────────────────────────────────┐
│  容器内普通用户  ──逃逸后──→  宿主机普通用户          │
│      │                            │                    │
│      └── 权限受限,危害降低 ─────→ 无法控制系统       │
└────────────────────────────────────────────────────────┘

基本用法

创建并切换用户

FROM node:20-alpine

# 1. 创建用户和组
RUN addgroup -g 1001 appgroup && \
    adduser -u 1001 -G appgroup -D appuser

# 2. 设置目录权限
WORKDIR /app
COPY --chown=appuser:appgroup . .

# 3. 切换用户
USER appuser

# 4. 后续命令以 appuser 身份运行
CMD ["node", "server.js"]

使用 UID/GID

# 也可以使用数字
USER 1001:1001

用户必须已存在

USER 指令只能切换到已存在的用户:

# ❌ 错误:用户不存在
USER nonexistent
# Error: unable to find user nonexistent

# ✅ 正确:先创建用户
RUN useradd -r -s /bin/false appuser
USER appuser

创建用户的方式

Debian/Ubuntu

RUN groupadd -r appgroup && \
    useradd -r -g appgroup appuser

Alpine

RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S -G appgroup appuser
选项 说明
-r (useradd) / -S (adduser) 创建系统用户
-g 指定主组
-G 指定附加组
-u 指定 UID
-s /bin/false 禁用登录 shell

运行时切换用户

使用 gosu推荐

在 ENTRYPOINT 脚本中切换用户时,不要使用 susudo,应使用 gosu

FROM debian:bookworm

# 创建用户
RUN groupadd -r redis && useradd -r -g redis redis

# 安装 gosu
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]

docker-entrypoint.sh

#!/bin/bash
set -e

# 以 root 执行初始化
chown -R redis:redis /data

# 用 gosu 切换到 redis 用户运行服务
exec gosu redis "$@"

为什么不用 su/sudo

问题 su/sudo gosu
TTY 要求 需要 不需要
信号传递 不正确 正确
子进程 exec 替换
容器中使用

运行时覆盖用户

使用 -u--user 参数:

# 以指定用户运行
$ docker run -u 1001:1001 myimage

# 以 root 运行(调试时)
$ docker run -u root myimage

文件权限处理

切换用户后,确保应用有权访问文件:

FROM node:20-alpine

# 创建用户
RUN adduser -D -u 1001 appuser

WORKDIR /app

# 方式1使用 --chown
COPY --chown=appuser:appuser . .

# 方式2手动 chown减少层数
# COPY . .
# RUN chown -R appuser:appuser /app

USER appuser
CMD ["node", "server.js"]

最佳实践

1. 始终使用非 root 用户

# ✅ 推荐
RUN adduser -D appuser
USER appuser
CMD ["myapp"]

# ❌ 避免
CMD ["myapp"]  # 以 root 运行

2. 使用固定 UID/GID

便于在宿主机和容器间共享文件:

# 使用常见的非 root UID
RUN addgroup -g 1000 -S appgroup && \
    adduser -u 1000 -S -G appgroup appuser
USER 1000:1000

3. 多阶段构建中的 USER

# 构建阶段可以用 root
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build

# 生产阶段用非 root
FROM node:20-alpine
RUN adduser -D appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appuser /app/dist .
USER appuser
CMD ["node", "server.js"]

常见问题

Q: 权限被拒绝

permission denied: '/app/data.log'

解决:确保目录权限正确

RUN mkdir -p /app/data && chown appuser:appuser /app/data

Q: 无法绑定低于 1024 的端口

非 root 用户无法绑定 80、443 等端口。

解决

  1. 使用高端口(如 8080
  2. 在运行时映射端口:docker run -p 80:8080

本章小结

要点 说明
作用 切换后续指令的执行用户
语法 USER usernameUSER UID:GID
前提 用户必须已存在
运行时覆盖 docker run -u
切换工具 使用 gosu不用 su/sudo

延伸阅读