Docker 镜像构建完全指南
镜像构建是 Docker 进阶的核心技能,熟练掌握 Dockerfile 编写和镜像构建技巧,能够显著提升开发和部署效率。本文系统讲解 Dockerfile 语法、构建缓存、多阶段构建等高级主题。
Dockerfile 基础
基本结构
Dockerfile 是构建镜像的蓝图文件,每一行指令都会创建一层。
dockerfile
# 基础镜像(必须的第一条指令)
FROM ubuntu:20.04
# 维护者信息(已废弃,用 LABEL 代替)
# MAINTAINER Your Name <email@example.com>
# 镜像元数据
LABEL maintainer="you@example.com"
LABEL version="1.0"
LABEL description="这是一个示例镜像"
# 环境变量
ENV APP_HOME /app
ENV NODE_ENV=production
# 工作目录
WORKDIR /app
# 复制文件到镜像
COPY package.json /app/
COPY ./src /app/src
# 运行命令
RUN npm install
# 暴露端口
EXPOSE 3000
# 容器启动命令
CMD ["node", "app.js"]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
常用指令详解
FROM — 指定基础镜像
dockerfile
# 官方镜像
FROM node:18-alpine
FROM python:3.11-slim
FROM nginx:alpine
# 私有仓库镜像
FROM registry.example.com/myimage:tag
# Scratch(空镜像,适合 Go/C 等静态编译语言)
FROM scratch
COPY hello /hello
CMD ["/hello"]1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
RUN — 执行命令
dockerfile
# shell 形式(调用 /bin/sh -c)
RUN apt-get update && apt-get install -y nginx
RUN echo "Hello" > /tmp/test.txt
# exec 形式(直接调用,不经过 shell)
RUN ["apt-get", "update"]
RUN ["python", "-m", "pip", "install", "flask"]
# 最佳实践:合并多条命令减少层数
RUN apt-get update && \
apt-get install -y nginx curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
COPY vs ADD
dockerfile
# COPY - 复制文件(推荐,明确)
COPY package.json /app/
COPY ./local/file.txt /app/file.txt
# ADD - 复制文件(功能更多但复杂)
ADD file.tar.gz /app/ # 自动解压tar.gz
ADD http://example.com/file.zip /app/ # 下载远程文件1
2
3
4
5
6
7
2
3
4
5
6
7
推荐使用 COPY,除非需要 ADD 的特殊功能。
WORKDIR — 设置工作目录
dockerfile
WORKDIR /app
WORKDIR /app/src
WORKDIR /logs
RUN pwd # 输出 /logs1
2
3
4
2
3
4
注意: WORKDIR 会自动创建目录,避免用 RUN cd 切换目录。
ENV — 环境变量
dockerfile
ENV APP_HOME /app
ENV DB_HOST=localhost DB_PORT=5432
# 可通过 docker run -e 覆盖
# docker run -e APP_HOME=/custom/path myimage1
2
3
4
5
2
3
4
5
EXPOSE — 声明端口
dockerfile
EXPOSE 80
EXPOSE 443 8080
# 只是文档作用,实际端口映射靠 -p 参数
# docker run -p 8080:80 myimage1
2
3
4
5
2
3
4
5
CMD 与 ENTRYPOINT — 启动命令
这是最容易混淆的两个指令:
dockerfile
# CMD:容器启动时的默认命令,可被 docker run 覆盖
CMD ["node", "app.js"]
CMD python app.py
CMD ["sh", "-c", "echo $MY_VAR"]
# ENTRYPOINT:固定入口,不容易被覆盖
ENTRYPOINT ["python", "app.py"]
# docker run myimage --help → python app.py --help1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
两者组合用法(推荐):
dockerfile
# ENTRYPOINT 定义主程序,CMD 传递默认参数
FROM python:3.11-slim
COPY ./app /app
WORKDIR /app
ENTRYPOINT ["python", "app.py"]
CMD ["--help"]
# 运行: docker run myimage → python app.py --help
# 运行: docker run myimage --version → python app.py --version1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
构建命令
基本构建
bash
# 构建镜像
docker build -t myapp:1.0 .
# -t: 指定镜像名:标签
# .: 指定构建上下文(当前目录)
# 不加标签构建(默认为 latest)
docker build -t myapp .
# 指定 Dockerfile 路径
docker build -f /path/to/Dockerfile -t myapp:1.0 .
# 多标签构建
docker build -t app:1.0 -t app:latest -t app:1.0.0 .1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
构建缓存
Docker 会缓存每一层,构建新镜像时尽可能复用缓存。
bash
# 强制不使用缓存(重新构建所有层)
docker build --no-cache -t myapp .
# 查看缓存使用情况
docker build -t myapp . --progress=plain1
2
3
4
5
2
3
4
5
利用缓存的最佳实践:
dockerfile
# 充分利用缓存的 Dockerfile
FROM node:18-alpine
# 先复制依赖文件(变化少)
COPY package*.json ./
# 安装依赖(缓存这一层)
RUN npm ci --only=production
# 再复制源代码(变化频繁)
COPY . .
CMD ["node", "app.js"]1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
上面这种写法的好处:修改源代码(不修改依赖)时,npm ci 层会被缓存,不用重新下载依赖。
破坏缓存的写法:
dockerfile
# 每次都会重新 npm install(不推荐)
FROM node:18-alpine
COPY . . # 源代码先复制
RUN npm install # 源代码一变就要重装依赖1
2
3
4
2
3
4
.dockerignore 文件
类似 .gitignore,排除不需要的文件进入构建上下文:
txt
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
.env
*.md
dist
coverage
.vscode1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
好处:
- 减少构建上下文大小(加快传输)
- 避免敏感文件进入镜像
- 避免 node_modules 重复构建(因为通常在 RUN npm install 中安装)
查看构建历史
bash
docker history myapp:1.0
# IMAGE CREATED SIZE
# a1b2c3d4e5f6 2 minutes ago 12.3MB
# <missing> 24 hours ago 124MB # node:18-alpine1
2
3
4
5
2
3
4
5
多阶段构建
多阶段构建是构建生产级镜像的核心技术,可以大幅减小镜像体积。
解决的问题
- 开发镜像包含完整的编译工具,体积庞大
- 源代码和中间产物不应该出现在生产镜像中
- 需要在多个镜像中重复构建相同内容
基础示例
dockerfile
# 第一阶段:构建
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
# 编译 Go 应用为静态二进制
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .
# 第二阶段:运行(极简镜像)
FROM alpine:latest
WORKDIR /app
# 只复制编译好的二进制文件
COPY --from=builder /app/myapp .
EXPOSE 8080
CMD ["./myapp"]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
对比效果
bash
# 单阶段构建(包含所有构建工具)
# 镜像大小: 800MB+
# 多阶段构建(只包含运行时)
# 镜像大小: 15MB1
2
3
4
5
2
3
4
5
Java/Maven 多阶段构建
dockerfile
# 第一阶段:构建
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline # 预下载依赖
COPY src ./src
RUN mvn package -DskipTests
# 第二阶段:运行
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/myapp.jar .
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "myapp.jar"]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Node.js 多阶段构建
dockerfile
# 第一阶段:构建
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 第二阶段:运行
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/main.js"]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在多阶段构建中引用任意阶段
dockerfile
# 复制其他阶段的产物
COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/
# 使用数字索引(从0开始)
COPY --from=0 /app/dist ./dist
# 使用 AS 定义的名字
COPY --from=builder /app/dist ./dist1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
最终阶段选择
bash
# 默认使用最后一个 FROM
# 但可以指定只构建某个阶段
docker build --target builder -t myapp:builder .1
2
3
2
3
实战:常见 Dockerfile 模板
Python 应用
dockerfile
FROM python:3.11-slim
WORKDIR /app
# 先复制依赖文件(利用缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再复制代码
COPY . .
ENV PYTHONUNBUFFERED=1
ENV GUNICORN_WORKERS=4
EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Node.js 应用
dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
USER node
CMD ["node", "server.js"]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
前后端分离(Nginx 代理)
dockerfile
# 多阶段:前端构建
FROM node:18-alpine AS frontend
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 后端构建
FROM maven:3.9-eclipse-temurin-17 AS backend
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
# 最终镜像
FROM nginx:alpine
# 复制前端静态文件
COPY --from=frontend /app/dist /usr/share/nginx/html
# 复制 nginx 配置
COPY nginx.conf /etc/nginx/nginx.conf
# 复制后端 jar
COPY --from=backend /app/target/myapp.jar /app/myapp.jar
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
镜像安全优化
dockerfile
# 使用最小化基础镜像
FROM alpine:3.18 # 比 ubuntu 小很多
FROM gcr.io/distroless/static # 更小,只包含二进制
FROM scratch # 最极端,什么都没有
# 不使用 root 运行
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# 减少层数
RUN apt-get update && \
apt-get install -y package1 package2 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 不保存缓存
RUN npm ci --only=production --no-cache
# 使用固定版本而非 latest
FROM python:3.11.8-slim-bookworm # 而不是 python:latest1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
镜像构建实战流程
bash
# 1. 本地构建测试
docker build -t myapp:dev .
# 2. 本地运行测试
docker run -d -p 8080:8080 --name test-app myapp:dev
docker logs -f test-app
curl localhost:8080/health
docker stop test-app && docker rm test-app
# 3. 打标签准备推送
docker tag myapp:dev registry.example.com/myapp:1.0.0
# 4. 登录仓库
docker login registry.example.com
# 5. 推送镜像
docker push registry.example.com/myapp:1.0.01
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
常见构建问题
1. 构建失败:网络问题
dockerfile
# 解决方案:使用国内镜像
RUN pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package
# 或在 daemon.json 中配置 registry 镜像
# /etc/docker/daemon.json
{
"registry-mirrors": ["https://mirror.ccs.tencentyun.com"]
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
2. 构建失败:权限问题
dockerfile
# 容器内使用非 root 用户
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser1
2
3
2
3
3. 镜像体积过大
- 使用 alpine 等精简镜像
- 使用多阶段构建分离构建和运行环境
- 使用 .dockerignore 排除不需要的文件
- 合并 RUN 指令减少层数
- 清理不必要的缓存和临时文件
4. 层缓存失效
修改顺序:把不常变化的指令(如 COPY 依赖文件、RUN 安装)放前面,变化频繁的指令(如 COPY 源代码)放后面。
命令速查
| 命令 | 说明 |
|---|---|
docker build -t myapp . | 构建镜像 |
docker build --no-cache . | 强制重新构建 |
docker build -f Dockerfile.dev . | 指定 Dockerfile 路径 |
docker build --target builder -t myapp:build . | 只构建某个阶段 |
docker history myimage | 查看镜像层历史 |
docker run myimage | 运行镜像 |
docker tag myapp myapp:1.0 | 给镜像打标签 |
docker push myapp:1.0 | 推送镜像 |
掌握 Dockerfile 编写和镜像构建技巧,是容器化开发的第一步。建议从本文的基础示例开始,逐步尝试多阶段构建和安全优化。
[[返回 Docker 首页|../index]]