# 7.11 USER 指定当前用户

## 7.11.1 基本语法

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

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

***

## 7.11.2 为什么要使用 USER

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

{% @mermaid/diagram content="flowchart LR
subgraph Root \["root 用户运行的风险："]
direction TB
R\_C\["容器内 root"] -- 可能逃逸 --> R\_H\["宿主机 root"]
R\_C -- 漏洞利用 --> R\_Control\["完全控制宿主机"]
end

```
subgraph NonRoot ["非 root 用户运行："]
    direction TB
    NR_C["容器内普通用户"] -- 逃逸后 --> NR_H["宿主机普通用户"]
    NR_C -- 权限受限，危害降低 --> NR_Safe["无法控制系统"]
end" %}
```

***

## 7.11.3 基本用法

### 创建并切换用户

```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
```

***

## 7.11.4 用户必须已存在

`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 |

***

## 7.11.5 运行时切换用户

### 使用 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 替换 |
| 容器中使用  | ❌       | ✅       |

***

## 7.11.6 运行时覆盖用户

使用 `-u` 或 `--user` 参数：

```bash
## 以指定用户运行

$ docker run -u 1001:1001 myimage

## 以 root 运行（调试时）

$ docker run -u root myimage
```

***

## 7.11.7 文件权限处理

切换用户后，确保应用有权访问文件：

```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"]
```

***

## 7.11.8 最佳实践

### 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"]
```

***

## 7.11.9 常见问题

### 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`

***
