This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/gyh/docker/Dockerfile编写指南.md
T

492 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Dockerfile 编写指南
> Dockerfile 是**镜像构建的蓝图**,每一行指令都会在镜像中生成一层。写好 Dockerfile 的核心目标:**镜像更小、构建更快、启动更安全**。
---
## 一、Dockerfile 基本结构
```dockerfile
# 1. 基础镜像
FROM node:20-slim AS builder
# 2. 维护者信息(可选)
LABEL maintainer="yourname@example.com"
# 3. 设置工作目录
WORKDIR /app
# 4. 复制依赖文件并安装
COPY package*.json ./
RUN npm ci --production
# 5. 复制源代码
COPY . .
# 6. 构建(开发环境需要,生产环境不需要)
RUN npm run build
# 7. 暴露端口
EXPOSE 3000
# 8. 启动命令
CMD ["node", "dist/index.js"]
```
---
## 二、常用指令详解
### 2.1 `FROM` — 基础镜像
```dockerfile
# 选镜像的通用原则:
# 有 Alpine 选 Alpine(体积小)
# 有 Slim 选 Slim(平衡体积和功能)
# 没有再选完整版本
FROM node:20-alpine # 推荐:最小体积
FROM node:20-slim # 推荐:平衡方案
FROM node:20 # 不推荐:包含完整 OS,镜像臃肿
FROM python:3.12-slim-bookworm
FROM nginx:alpine
FROM golang:1.22-alpine
FROM ubuntu:24.04 # 一般作为多阶段构建的第一步
```
**常见基础镜像体积对比:**
| 镜像 | 大小 | 适用场景 |
|------|------|----------|
| `alpine` | 5-10MB | 生产环境、追求极致体积 |
| `slim` | 100-200MB | 大多数生产场景,兼容性好 |
| 完整版 | 1GB+ | 开发环境、需要完整工具链 |
### 2.2 `RUN` — 执行命令
```dockerfile
# 合并 RUN 指令减少镜像层数
# 不推荐:每行一个 RUN,产生多层
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
# 推荐:合并为一行,并用 && 连接,用 \ 换行
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
# 注意:RUN 产生的层会**永久留在镜像中**,即使后续 rm 了也没用
# 所以更新和安装、清理要放在同一个 RUN 里
```
### 2.3 `COPY` vs `ADD`
```dockerfile
# COPY — 简单复制,推荐优先使用
COPY package.json ./
COPY . .
# ADD — 有额外功能但不可预测,不推荐
ADD https://example.com/file.tar.gz /tmp/ # 能下载远程文件,但容易出错
ADD file.tar.gz /app/ # 能自动解压,但 COPY + RUN tar 更清晰
# 结论:99% 的情况用 COPY,ADD 只在自动解压 tar 和下载远程文件时有用
```
### 2.4 `WORKDIR` — 工作目录
```dockerfile
WORKDIR /app # 设置工作目录
COPY package.json ./ # 相对于 /app
RUN npm install
COPY . . # 复制代码到 /app
# WORKDIR 会被后续指令继承,且会自动创建(不需要提前 mkdir)
WORKDIR /subdir # 现在是 /app/subdir
RUN pwd # 输出 /app/subdir
```
### 2.5 `EXPOSE` — 声明端口
```dockerfile
# 声明容器运行时监听的端口(文档性质,不自动映射)
EXPOSE 3000
EXPOSE 80 443
# 实际端口映射需要在 docker run -p 或 docker-compose 中指定
```
### 2.6 `ENV` — 环境变量
```dockerfile
ENV NODE_ENV=production
ENV APP_PORT=3000
ENV DB_HOST=localhost
# 容器启动时可以覆盖:
# docker run -e DB_HOST=192.168.1.100 my-app
```
### 2.7 `CMD` vs `ENTRYPOINT`
```dockerfile
# CMD — 容器启动时执行的默认命令(可被覆盖)
CMD ["node", "dist/index.js"]
# 运行 docker run my-app echo "hello" 会覆盖 CMD
# ENTRYPOINT — 入口点,不可被 docker run 覆盖(但可用 --entrypoint 强制覆盖)
ENTRYPOINT ["node"]
CMD ["dist/index.js"] # 作为 ENTRYPOINT 的参数
# 实际开发中,大多数情况用 CMD 就够了
```
---
## 三、多阶段构建(关键优化手段)
### 3.1 为什么需要多阶段构建
前端项目用 Next.js/Vite + TypeScript 构建,需要 Node.js 全套环境。但运行只需要一个编译后的静态文件。多阶段构建可以**把构建工具留在第一阶段,最终镜像只放产物**。
### 3.2 前端多阶段构建(Node + TypeScript)
```dockerfile
# ========== 第一阶段:构建 ==========
FROM node:20-alpine AS builder
WORKDIR /app
# ① 先复制依赖文件,利用 Docker 缓存
COPY package*.json ./
RUN npm ci
# ② 再复制源码(代码改了才重新构建)
COPY . .
# ③ 构建
RUN npm run build
# ========== 第二阶段:运行 ==========
FROM node:20-alpine AS production
# 安装 tini(处理僵尸进程,可选但推荐)
RUN apk add --no-cache tini
WORKDIR /app
# 仅复制 production 依赖
COPY package*.json ./
RUN npm ci --production
# 从构建阶段复制产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/public ./public
ENV NODE_ENV=production
EXPOSE 3000
# 使用 tini 作为 PID 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/index.js"]
```
**核心思路:先用大镜像构建 → 再把产物复制到最小镜像中运行。**
### 3.3 Java 多阶段构建
```dockerfile
# 第一阶段:Maven 构建
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests
# 第二阶段:运行
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
```
### 3.4 Go 多阶段构建
```dockerfile
# 第一阶段:编译
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server
# 第二阶段:运行
FROM alpine:3.19
RUN apk --no-cache add ca-certificates
COPY --from=builder /app/server /app/server
EXPOSE 8080
CMD ["/app/server"]
```
---
## 四、.dockerignore 文件
> **和 `.gitignore` 一样重要!** 不写 `.dockerignore` 会导致不必要的文件被复制到镜像中,增大体积、暴露敏感信息。
```dockerignore
# 依赖
node_modules
npm-debug.log
# Git
.git
.gitignore
# 构建产物(别把 build 直接拷进去,让容器自己 build)
dist
build
*.local
# 编辑器
.vscode
.idea
*.swp
# Docker 自身
docker-compose.yml
Dockerfile
.dockerignore
# 文档和测试
README.md
LICENSE
tests
__tests__
# 环境变量(千万不要拷进去!)
.env
.env.local
.env.*.local
```
---
## 五、最佳实践清单
### ✅ 应该做
| 实践 | 说明 |
|------|------|
| 使用具体版本号 | `FROM node:20.11-alpine`,不要用 `latest` |
| 用 `alpine` 或 `slim` | 减小镜像体积 |
| 合并 `RUN` 指令 | 减少镜像层数 |
| 先复制 `package.json` 再 `npm install` | 利用 Docker 层缓存 |
| 用 `.dockerignore` | 排除不必要的文件 |
| 用多阶段构建 | 分离构建和运行环境 |
| 非 root 用户运行 | 安全考虑 |
| 用 `CMD ["exec格式"]` | 避免 shell 包装问题 |
### ❌ 不应该做
| 实践 | 问题 |
|------|------|
| 用 `latest` 标签 | 构建结果不可复现 |
| 在 Dockerfile 中存密码/密钥 | 暴露敏感信息 |
| 用 `ADD` 替代 `COPY` | 不可预测行为 |
| 安装不必要的应用 | 镜像臃肿 |
| 用 `RUN apt-get install && apt-get remove` | 删除的文件仍在镜像层中 |
| 以 root 用户运行应用 | 安全风险 |
---
## 六、非 Root 用户示例
```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
# 创建非 root 用户
RUN addgroup -S appgroup && \
adduser -S appuser -G appgroup
# 切换用户
USER appuser
EXPOSE 3000
CMD ["node", "dist/index.js"]
```
---
## 七、常见问题排查
### 7.1 镜像太大
```bash
# 分析镜像各层大小
docker history my-app:latest
# 检查是否有未清理的缓存
# 解决:使用 alpine/slim 基础镜像 + 多阶段构建 + .dockerignore
```
### 7.2 构建缓存失效
```dockerfile
# 错误:代码改动导致 npm install 也重新执行
COPY . .
RUN npm install
# 正确:依赖文件不变时跳过 npm install
COPY package*.json ./
RUN npm install
COPY . .
```
### 7.3 容器启动后立即退出
```bash
# 查看日志
docker logs <容器名>
# 常见原因:
# 1. CMD/ENTRYPOINT 写错了
# 2. 主进程不是 PID 1(用 tini 或 dumb-init 处理)
# 3. 应用启动失败(检查日志)
```
### 7.4 端口无法访问
```bash
# 1. 检查 docker run -p 是否映射
docker run -p 3000:3000 ...
# 2. 检查容器内应用是否监听 0.0.0.0(不是 localhost)
# 3. 检查防火墙
# 4. 用 docker-compose 时,确认 services 定义正确
```
---
## 七、Docker 安全加固
### 7.1 使用非 root 用户
```dockerfile
# 最简方式(Node 官方镜像已内置 node 用户)
FROM node:20-alpine
USER node
CMD ["node", "server.js"]
# 手动创建用户(通用模式)
FROM node:20-alpine
WORKDIR /app
COPY --chown=node:node package*.json ./
RUN npm ci
COPY --chown=node:node . .
USER node
CMD ["node", "dist/index.js"]
```
> `--chown=user:group` 在 COPY/RUN 时设置文件所有者,**避免 root 拥有文件**后切换到非 root 用户导致权限问题。
### 7.2 不要安装 build 依赖到生产镜像
```dockerfile
# 错误:所有依赖都装进去了
FROM node:20-alpine
RUN npm install # devDependencies 也被安装了
# 正确:生产环境只装 dependencies
RUN npm ci --only=production
# 更正确:多阶段构建
FROM node:20-alpine AS builder
RUN npm install # 包含 devDependencies
RUN npm run build
FROM node:20-alpine
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
```
### 7.3 扫描镜像漏洞
```bash
# 使用 trivy(推荐)
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image nginx:latest
# 扫描指定镜像
trivy image my-app:v1.0
# 只输出高危及以上
trivy image --severity HIGH,CRITICAL my-app:v1.0
# 扫描 Dockerfile 本身(不用构建镜像)
trivy config Dockerfile
```
### 7.4 密钥管理原则
```dockerfile
# ❌ 绝对不要硬编码密码
RUN echo "password=123456" > /app/config.env
# ❌ 不要在构建时传递密钥(会留在镜像层中)
docker build --build-arg DB_PASSWORD=secret .
# ✅ 运行时传入环境变量
docker run -e DB_PASSWORD=secret my-app
# ✅ 或使用 Docker secrets(Swarm 模式)
# ✅ 生产环境用 Kubernetes Secrets / AWS Secrets Manager
```
---
## 八、实战:Node.js + Express 完整 Dockerfile
```dockerfile
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Production stage
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./package.json
ENV NODE_ENV=production
ENV PORT=3000
RUN addgroup -S app && adduser -S app -G app
USER app
EXPOSE 3000
CMD ["node", "dist/server.js"]
```
---
> 下一步:理解 Dockerfile 后,学习 [[docker-compose]] 来编排多容器应用。