# Dockerfile 优化 + 镜像审计报告

**审计日期**: 2026-06-15
**审计人**: Claude
**关联**: [[风险跟踪表]] / [[SOP-001-Gitea部署]]

---

## 📌 1. 现状盘点

| 镜像 | Dockerfile | 基础 | 估计大小 | 多阶段 |
|---|---|---|---|---|
| backend | `backend/Dockerfile` | `python:3.12-slim` | ~250 MB | ✅ |
| frontend-agent | `frontend-agent/Dockerfile` | `nginx:1.27-alpine` | ~50 MB | ✅ |
| frontend-h5 | `frontend-h5/Dockerfile` | `nginx:1.27-alpine` | ~50 MB | ✅ |
| postgres | (用官方) | `postgres:16-alpine` | ~80 MB | — |
| redis | (用官方) | `redis:7-alpine` | ~30 MB | — |
| nginx | (用官方) | `nginx:1.27-alpine` | ~40 MB | — |

**总估计镜像大小**:`~500 MB`(4 业务 + 2 数据库)

---

## 📌 2. backend Dockerfile 审计

### 2.1 当前实现

```dockerfile
FROM python:3.12-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc libpq-dev libjpeg-dev zlib1g-dev curl
COPY requirements.txt .
RUN pip install --no-cache-dir --timeout 120 --retries 5 \
    -i https://pypi.tuna.tsinghua.edu.cn/simple/ \
    --trusted-host pypi.tuna.tsinghua.edu.cn \
    -r requirements.txt

FROM python:3.12-slim
RUN apt-get update && apt-get install -y --no-install-recommends libpq5 curl
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

### 2.2 问题清单

| # | 问题 | 严重度 | 优化 |
|---|---|---|---|
| B-1 | ⚠️ **装 curl** — 但 P1-3 已改 healthcheck 用 Python urllib | 🟡 | 删 curl(节省 1MB) |
| B-2 | ⚠️ **不用非 root 用户** | 🟠 中 | 加 `USER appuser` |
| B-3 | ⚠️ **没 HEALTHCHECK** — 交给 docker-compose | 🟡 | Dockerfile 也加 |
| B-4 | ⚠️ **COPY . . 太宽** — 含 .git / tests / docs | 🟡 | 加 .dockerignore |
| B-5 | 🟢 pip 装到 venv(更隔离) | 🟢 | 已用 site-packages |
| B-6 | ⚠️ **没用 BuildKit cache mount** | 🟡 | 加 `--mount=type=cache` |
| B-7 | ⚠️ **PyPI 用清华源** — 公司内网可,但生产建议官方 | 🟡 | 评估 |

### 2.3 优化版

```dockerfile
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim AS builder

# 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

WORKDIR /app

# 系统依赖(只装构建期需要的)
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && \
    apt-get install -y --no-install-recommends \
        gcc libpq-dev libjpeg-dev zlib1g-dev && \
    rm -rf /var/lib/apt/lists/*

# 依赖(用 cache mount + BuildKit)
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install --no-cache-dir --user \
    --timeout 120 --retries 5 \
    -i https://pypi.tuna.tsinghua.edu.cn/simple/ \
    --trusted-host pypi.tuna.tsinghua.edu.cn \
    -r requirements.txt

# 运行镜像
FROM python:3.12-slim

# 复制非 root 用户
COPY --from=builder /etc/passwd /etc/passwd
COPY --from=builder /etc/group /etc/group

# 运行时依赖(只 libpq5,**不装 curl**)
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    apt-get update && \
    apt-get install -y --no-install-recommends libpq5 && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 复制构建好的 Python 包
COPY --from=builder /root/.local /home/appuser/.local
COPY --chown=appuser:appuser . .

# 切非 root 用户
USER appuser

ENV PATH=/home/appuser/.local/bin:$PATH
ENV PYTHONUNBUFFERED=1

EXPOSE 8000

# 内置 healthcheck(不依赖 curl)
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health').read()"

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

**预期收益**:
- 镜像小 ~10 MB(删 curl)
- 安全(非 root)
- 加速 rebuild(BuildKit cache)
- 内置 healthcheck(无需依赖 compose)

---

## 📌 3. frontend Dockerfile 审计(agent + h5 同)

### 3.1 当前实现

```dockerfile
FROM node:20-slim AS builder
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```

### 3.2 问题清单

| # | 问题 | 严重度 | 优化 |
|---|---|---|---|
| F-1 | ⚠️ **不用非 root**(nginx 默认 root) | 🟠 中 | 自定义 nginx.conf 改 user |
| F-2 | ⚠️ **没 nginx.conf** — 用默认 | 🟡 | 复制 custom nginx.conf |
| F-3 | ⚠️ **没 .dockerignore** | 🟡 | 加 |
| F-4 | ⚠️ **没 layer cache 优化** | 🟡 | BuildKit cache mount |
| F-5 | ⚠️ **不用 alpine node** | 🟡 | 改 `node:20-alpine` |

### 3.3 优化版

```dockerfile
# syntax=docker/dockerfile:1.7
FROM node:20-alpine AS builder

WORKDIR /app

# 装 pnpm(快 2-3 倍,磁盘省 50%)
RUN corepack enable && corepack prepare pnpm@9 --activate

# 依赖
COPY package.json pnpm-lock.yaml* ./
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
    pnpm install --frozen-lockfile

# 源码 + 构建
COPY . .
RUN pnpm run build

# 运行镜像
FROM nginx:1.27-alpine

# 自定义 nginx.conf(非 root + 反代配置)
COPY nginx.conf /etc/nginx/nginx.conf

# 从 builder 复制 dist
COPY --from=builder --chown=nginx:nginx /app/dist /usr/share/nginx/html

# nginx alpine 默认是 nginx user
USER nginx

EXPOSE 80

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD wget -q --spider http://localhost/ || exit 1

CMD ["nginx", "-g", "daemon off;"]
```

**预期收益**:
- 用 pnpm 代替 npm(快 2-3 倍)
- alpine node 镜像小 ~150 MB
- 自定义 nginx.conf + 非 root
- 内置 healthcheck

---

## 📌 4. .dockerignore 建议

**根目录** `.dockerignore`:
```
# Git
.git/
.gitignore
.gitattributes
.git-blame-ignore-revs

# 文档
docs/
*.md
!backend/README.md

# 测试
tests/
**/test_*.py
**/*_test.py
**/*.test.ts
**/*.spec.ts
coverage/
.coverage
htmlcov/
.pytest_cache/

# 开发工具
.vscode/
.idea/
*.swp
.DS_Store
Thumbs.db

# 构建产物(各端 dist)
frontend-*/dist/
frontend-*/node_modules/

# 部署包 / 备份
deploy-*.tar
deploy-*.tar.gz
*.log
*.log.err
build_logs/

# Python
__pycache__/
*.py[cod]
*$py.class
.venv/
venv/
*.egg-info/

# 环境变量(敏感)
.env
.env.*
!.env.example

# Docker
Dockerfile
.dockerignore
docker-compose*.yml
```

**每个前端** `frontend-X/.dockerignore`:
```
node_modules/
dist/
.env
.env.*
```

---

## 📌 5. 镜像大小优化(整体)

| 优化项 | 节省 | 风险 |
|---|---|---|
| backend 删 curl | 1 MB | 无 |
| 前端换 `node:20-alpine` | ~150 MB × 2 | 无 |
| 前端用 pnpm | ~50 MB × 2 | 无 |
| 加 .dockerignore | ~30% build 体积 | 无 |
| 跑 `docker system prune` | 100-500 MB | 无 |
| **总节省** | **~400 MB** | — |

---

## 📌 6. 安全加固

### 6.1 当前问题

| # | 问题 | 严重度 |
|---|---|---|
| S-1 | 全部容器跑 root | 🟠 中 |
| S-2 | 没 secret 扫描(防 docker build 时 COPY 进 secret) | 🟡 |
| S-3 | 没镜像漏洞扫描(Trivy) | 🟡 |

### 6.2 修复

1. **所有 Dockerfile 加 `USER` 指令**(已写优化版)
2. **加 Trivy 扫描到 CI**:
   ```yaml
   # .gitea/workflows/security.yml
   - name: Run Trivy vulnerability scanner
     uses: aquasecurity/trivy-action@master
     with:
       image-ref: 'wecom-it-desk-backend:latest'
       format: 'table'
       exit-code: '1'
       ignore-unfixed: true
   ```
3. **加 secret 扫描**:
   - `.gitleaks.toml` 配 gitleaks
   - pre-commit hook 跑 gitleaks

---

## 📌 7. 构建性能

| 优化 | 加速 | 实现 |
|---|---|---|
| BuildKit cache mount | 3-5x | `RUN --mount=type=cache,target=...` |
| 多阶段 | 减少最终大小 | 已用 |
| 依赖层缓存 | 2-3x | `COPY requirements.txt` 先于 `COPY .` |
| 并行构建 | 2-3x | `docker buildx build` |
| 镜像 registry 缓存 | 1.5-2x | 推 Gitea Container Registry |

---

## 📌 8. 实施路径

### 8.1 立即(本次跑批)

- [x] 审计报告写完(本文件)
- [ ] 加根目录 `.dockerignore`
- [ ] 加每个前端 `.dockerignore`

### 8.2 下周

- [ ] backend Dockerfile 优化版(删 curl + 非 root + healthcheck)
- [ ] frontend Dockerfile 优化版(alpine + pnpm + 非 root)
- [ ] 跑 `docker build` 验证大小

### 8.3 季度

- [ ] 加 Trivy 扫描到 CI
- [ ] 加 Gitea Container Registry
- [ ] 多架构构建(amd64 + arm64)

---

## 📌 9. 风险与缓解

| 风险 | 等级 | 缓解 |
|---|---|---|
| 优化版 Dockerfile 漏改回归 | 🟡 中 | CI 跑 `docker build` 测试 |
| alpine 镜像 musl libc 兼容性 | 🟡 中 | 验证 Python wheels |
| pnpm lockfile 跟 npm 差异 | 🟢 低 | 用 `pnpm import` 转 |
| 非 root 用户文件权限 | 🟡 中 | `chown` 显式指定 |

---

## 📌 10. 关联文档

- [[风险跟踪表]] M-11(数据库密码弱) / 部署相关
- [[SOP-001-Gitea部署]] - Gitea 部署参考
- [[Gitea部署指南]] - 部署文档

---

*本审计是 2026-06-15 Claude 满载跑批产出*
