## 7.11 USER 指定当前用户 ### 基本语法 具体内容如下: ```docker USER <用户名>[:<用户组>] USER [:] ``` `USER` 指令切换后续指令(RUN、CMD、ENTRYPOINT)的执行用户。 --- ### 为什么要使用 USER > 笔者强调:以非 root 用户运行容器是最重要的安全实践之一。 ``` root 用户运行的风险: ┌────────────────────────────────────────────────────────┐ │ 容器内 root ←─ 可能逃逸 ─→ 宿主机 root │ │ │ │ │ │ └── 漏洞利用 ───────────────→ 完全控制宿主机 │ └────────────────────────────────────────────────────────┘ 非 root 用户运行: ┌────────────────────────────────────────────────────────┐ │ 容器内普通用户 ──逃逸后──→ 宿主机普通用户 │ │ │ │ │ │ └── 权限受限,危害降低 ─────→ 无法控制系统 │ └────────────────────────────────────────────────────────┘ ``` --- ### 基本用法 #### 创建并切换用户 具体内容如下: ```docker 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 具体内容如下: ```docker ## 也可以使用数字 USER 1001:1001 ``` --- ### 用户必须已存在 `USER` 指令只能切换到**已存在**的用户: ```docker ## ❌ 错误:用户不存在 USER nonexistent ## Error: unable to find user nonexistent ## ✅ 正确:先创建用户 RUN useradd -r -s /bin/false appuser USER appuser ``` #### 创建用户的方式 **Debian/Ubuntu**: ```docker RUN groupadd -r appgroup && \ useradd -r -g appgroup appuser ``` **Alpine**: ```docker 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 脚本中切换用户时,不要使用 `su` 或 `sudo`,应使用 [gosu](https://github.com/tianon/gosu): ```docker 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**: ```bash #!/bin/bash set -e ## 以 root 执行初始化 chown -R redis:redis /data ## 用 gosu 切换到 redis 用户运行服务 exec gosu redis "$@" ``` #### 为什么不用 su/sudo | 问题 | su/sudo | gosu | |------|---------|------| | TTY 要求 | 需要 | 不需要 | | 信号传递 | 不正确 | 正确 | | 子进程 | 是 | exec 替换 | | 容器中使用 | ❌ | ✅ | --- ### 运行时覆盖用户 使用 `-u` 或 `--user` 参数: ```bash ## 以指定用户运行 $ docker run -u 1001:1001 myimage ## 以 root 运行(调试时) $ docker run -u root myimage ``` --- ### 文件权限处理 切换用户后,确保应用有权访问文件: ```docker 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 用户 具体内容如下: ```docker ## ✅ 推荐 RUN adduser -D appuser USER appuser CMD ["myapp"] ## ❌ 避免 CMD ["myapp"] # 以 root 运行 ``` #### 2. 使用固定 UID/GID 便于在宿主机和容器间共享文件: ```docker ## 使用常见的非 root UID RUN addgroup -g 1000 -S appgroup && \ adduser -u 1000 -S -G appgroup appuser USER 1000:1000 ``` #### 3. 多阶段构建中的 USER 具体内容如下: ```docker ## 构建阶段可以用 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: 权限被拒绝 运行以下命令: ```bash permission denied: '/app/data.log' ``` **解决**:确保目录权限正确 ```docker RUN mkdir -p /app/data && chown appuser:appuser /app/data ``` #### Q: 无法绑定低于 1024 的端口 非 root 用户无法绑定 80、443 等端口。 **解决**: 1. 使用高端口(如 8080) 2. 在运行时映射端口:`docker run -p 80:8080` --- ### 本章小结 | 要点 | 说明 | |------|------| | **作用** | 切换后续指令的执行用户 | | **语法** | `USER username` 或 `USER UID:GID` | | **前提** | 用户必须已存在 | | **运行时覆盖** | `docker run -u` | | **切换工具** | 使用 gosu,不用 su/sudo | ### 延伸阅读 - [安全](../../security/README.md):容器安全实践 - [ENTRYPOINT](entrypoint.md):入口脚本中的用户切换 - [最佳实践](../../16_appendix/16.1_best_practices.md):Dockerfile 安全