This commit is contained in:
Baohua Yang
2026-02-09 12:56:12 -08:00
parent 63377d0431
commit b44c9acd6c
228 changed files with 326 additions and 271 deletions

307
07_dockerfile/7.11_user.md Normal file
View File

@@ -0,0 +1,307 @@
## 7.11 USER 指定当前用户
### 基本语法
具体内容如下
```docker
USER <用户名>[:<用户组>]
USER <UID>[:<GID>]
```
`USER` 指令切换后续指令RUNCMDENTRYPOINT的执行用户
---
### 为什么要使用 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 用户无法绑定 80443 等端口
**解决**
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 安全