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

11 KiB
Raw Blame History

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 — 基础镜像

# 选镜像的通用原则:
#   有 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 — 执行命令

# 合并 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

# 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 — 工作目录

WORKDIR /app                        # 设置工作目录
COPY package.json ./                # 相对于 /app
RUN npm install
COPY . .                            # 复制代码到 /app

# WORKDIR 会被后续指令继承,且会自动创建(不需要提前 mkdir)
WORKDIR /subdir                     # 现在是 /app/subdir
RUN pwd                             # 输出 /app/subdir

2.5 EXPOSE — 声明端口

# 声明容器运行时监听的端口(文档性质,不自动映射)
EXPOSE 3000
EXPOSE 80 443

# 实际端口映射需要在 docker run -p 或 docker-compose 中指定

2.6 ENV — 环境变量

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

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

# ========== 第一阶段:构建 ==========
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 多阶段构建

# 第一阶段: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 多阶段构建

# 第一阶段:编译
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 会导致不必要的文件被复制到镜像中,增大体积、暴露敏感信息。

# 依赖
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 用户示例

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 镜像太大

# 分析镜像各层大小
docker history my-app:latest

# 检查是否有未清理的缓存
# 解决:使用 alpine/slim 基础镜像 + 多阶段构建 + .dockerignore

7.2 构建缓存失效

# 错误:代码改动导致 npm install 也重新执行
COPY . .
RUN npm install

# 正确:依赖文件不变时跳过 npm install
COPY package*.json ./
RUN npm install
COPY . .

7.3 容器启动后立即退出

# 查看日志
docker logs <容器名>

# 常见原因:
# 1. CMD/ENTRYPOINT 写错了
# 2. 主进程不是 PID 1(用 tini 或 dumb-init 处理)
# 3. 应用启动失败(检查日志)

7.4 端口无法访问

# 1. 检查 docker run -p 是否映射
docker run -p 3000:3000 ...

# 2. 检查容器内应用是否监听 0.0.0.0(不是 localhost)
# 3. 检查防火墙

# 4. 用 docker-compose 时,确认 services 定义正确

七、Docker 安全加固

7.1 使用非 root 用户

# 最简方式(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 依赖到生产镜像

# 错误:所有依赖都装进去了
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 扫描镜像漏洞

# 使用 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 密钥管理原则

# ❌ 绝对不要硬编码密码
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

# 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 来编排多容器应用。