Docker: Dockerfile基础
最后更新:2026-08-26
Dockerfile 是镜像的"源代码"——写好 Dockerfile,就能一键构建可复现的应用镜像。
1. 你将学到
- Dockerfile 基本语法和结构
- FROM 选择基础镜像的策略
- RUN 执行构建命令的最佳实践
- CMD 与 ENTRYPOINT 的区别
- 构建上下文与 .dockerignore
2. 一个 Python 开发者的真实故事
(1) 痛点:每次部署手动装环境
Alice 写了一个 Python Web 应用,每次部署都要手动安装 Python、配置虚拟环境、安装依赖、拷贝代码。测试服务器、预发布环境、生产服务器各操作一遍,总共 3 次重复劳动,且每次都有细微差异。
(2) Dockerfile 自动化的解法
Bob 说:"写一个 Dockerfile,把你的应用'装进'镜像,以后一行命令就能在任何地方跑。"
DOCKERFILE
# A simple Dockerfile for a Flask application
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
(3) 收益:一次构建,到处运行
Alice 用 docker build -t myapp:1.0 . 构建镜像后,三个环境用同一个镜像运行,部署从 30 分钟降到 3 分钟,环境差异问题归零。
3. Dockerfile 基本结构
Dockerfile 是一个纯文本文件,包含构建镜像的指令序列。每条指令构建一个镜像层。
graph LR
DF["Dockerfile<br/>指令序列"] -->|docker build| IMG["Image<br/>只读层叠加"]
IMG -->|docker run| CTN["Container<br/>可写层 + 只读层"]
(1) 指令执行时机
| 时机 | 指令 | 说明 |
|---|---|---|
| 构建时 | FROM / RUN / COPY / ADD / ARG |
生成镜像层 |
| 运行时 | CMD / ENTRYPOINT / ENV / EXPOSE / USER |
定义容器行为 |
(2) Dockerfile 基本模板
DOCKERFILE
# 1. Base image
FROM python:3.12-slim
# 2. Set working directory
WORKDIR /app
# 3. Copy dependency file first (cache optimization)
COPY requirements.txt .
# 4. Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# 5. Copy application code
COPY . .
# 6. Define the default command
CMD ["python", "app.py"]
4. FROM:选择基础镜像
FROM 是 Dockerfile 的第一条指令,指定构建的基础镜像。
(1) 基础镜像选择策略
| 策略 | 镜像 | 大小 | 适用场景 |
|---|---|---|---|
| 官方语言镜像 | python:3.12-slim |
155 MB | Python 项目 |
| Alpine 变体 | python:3.12-alpine |
50 MB | 磁盘极度紧张 |
| 多阶段构建 | golang:1.22 → alpine |
12 MB | Go/Rust 编译型语言 |
| 精简 OS | debian:bookworm-slim |
74 MB | 需要自定义环境 |
▶ 示例:最简 Dockerfile(难度⭐)
DOCKERFILE
# Minimal Dockerfile: just prints hello
FROM alpine:3.19
CMD ["echo", "Hello from Docker!"]
BASH
# Build and run
docker build -t hello:1.0 .
docker run --rm hello:1.0
💻 输出:
TEXT
📖 仅展示
Hello from Docker!
5. RUN:执行构建命令
RUN 在构建时执行命令,结果写入新的镜像层。
(1) 两种格式
| 格式 | 语法 | 特点 |
|---|---|---|
| Shell 形式 | RUN apt-get install nginx |
默认 /bin/sh -c 执行,支持管道 |
| Exec 形式 | RUN ["apt-get", "install", "nginx"] |
直接执行,不启动 shell |
▶ 示例:RUN 安装依赖(难度⭐⭐)
DOCKERFILE
# Best practice: combine RUN commands to reduce layers
FROM debian:bookworm-slim
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
nginx && \
rm -rf /var/lib/apt/lists/*
📌 重点: 将多个
apt-get 命令合并为一个 RUN,减少镜像层数。rm -rf /var/lib/apt/lists/* 清理 APT 缓存,减小镜像体积。
(2) RUN 链式合并的原则
| 做法 | 效果 | 说明 |
|---|---|---|
多个 RUN |
每条创建一层 | 层数多、体积大 |
合并 RUN && |
一条创建一层 | 层数少、体积小 |
| 清理缓存 | rm -rf apt/lists |
同层内清理有效 |
| 分开清理 | 下一个 RUN rm |
无效——上一层已固化 |
DOCKERFILE
# Bad: creates 2 layers, apt cache is baked into layer 1
RUN apt-get update
RUN apt-get install -y nginx
# Good: creates 1 layer, cache cleaned in the same layer
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
6. CMD 与 ENTRYPOINT
CMD 和 ENTRYPOINT 都定义容器启动时执行的命令,但行为不同。
(1) 三种启动指令对比
| 维度 | CMD |
ENTRYPOINT |
|---|---|---|
| 用途 | 提供默认命令 | 定义固定入口点 |
| 可覆盖 | docker run 参数直接覆盖 |
需 --entrypoint 才能覆盖 |
| 搭配 | 可与 ENTRYPOINT 搭配 | 可搭配 CMD 传参 |
| 多条 | 只有最后一条生效 | 只有最后一条生效 |
(2) CMD 的三种格式
| 格式 | 语法 | 推荐度 | 说明 |
|---|---|---|---|
| Exec 形式 | CMD ["python", "app.py"] |
⭐⭐⭐ | 直接执行,信号正确传递 |
| Shell 形式 | CMD python app.py |
⭐ | 作为 /bin/sh -c 子进程,SIGTERM 不传递 |
| 参数形式 | CMD ["--port", "8080"] |
⭐⭐ | 配合 ENTRYPOINT 使用 |
▶ 示例:CMD vs ENTRYPOINT 区别(难度⭐⭐)
DOCKERFILE
# Dockerfile with CMD: command can be easily overridden
FROM alpine:3.19
CMD ["echo", "Hello default"]
BASH
# Default: runs CMD
docker run --rm test-cmd
# Output: Hello default
# Override CMD with custom command
docker run --rm test-cmd echo "Custom message"
# Output: Custom message
DOCKERFILE
# Dockerfile with ENTRYPOINT: command stays, args are appended
FROM alpine:3.19
ENTRYPOINT ["echo"]
CMD ["Hello default"]
BASH
# Default: runs ENTRYPOINT + CMD
docker run --rm test-entry
# Output: Hello default
# Append arguments (don't override ENTRYPOINT)
docker run --rm test-entry "Custom message"
# Output: Custom message
# Override ENTRYPOINT (rarely needed)
docker run --rm --entrypoint sh test-entry -c "ls /"
▶ 示例:ENTRYPOINT + CMD 搭配模式(难度⭐⭐⭐)
这是最佳实践模式——ENTRYPOINT 固定执行程序,CMD 提供默认参数:
DOCKERFILE
# Entrypoint + CMD pattern
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "5000"]
BASH
# Default: uses CMD arguments
docker run --rm myapp
# Equivalent to: python app.py --host 0.0.0.0 --port 5000
# Override just the arguments
docker run --rm myapp --port 8080
# Equivalent to: python app.py --port 8080
7. 构建上下文与 .dockerignore
(1) 构建上下文
docker build 的最后一个参数 . 不是"当前目录"的意思,而是构建上下文(Build Context)——Docker Client 将这个目录的所有文件发送给 Daemon。
graph LR
CTX["Build Context<br/>(. 目录)"] -->|发送文件| D["Docker Daemon"]
D -->|Dockerfile 中的 COPY/ADD| IMG["Image Layers"]
⚠️ 注意: 如果目录中有 1 GB 的 node_modules,build 时也会发送 1 GB 给 Daemon(即使 Dockerfile 不 COPY 它)。
.dockerignore 可以排除不需要的文件。
▶ 示例:.dockerignore 效果(难度⭐⭐)
TEXT
📖 仅展示
# .dockerignore - exclude files from build context
node_modules
.git
__pycache__
*.pyc
.env
Dockerfile
docker-compose*.yml
README.md
.vscode
(2) 为什么 COPY 依赖文件要先于 COPY 源码
DOCKERFILE
# Good: dependency change rate < code change rate
# When code changes, dependency layer uses cache
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
# Bad: any file change invalidates the pip install layer
COPY . .
RUN pip install -r requirements.txt
| 变更场景 | Good 版 | Bad 版 |
|---|---|---|
| 只改代码 | ✅ pip 层用缓存(秒级) | ❌ pip 层重建(分钟级) |
| 改依赖 | ✅ pip 层重建(必要) | ❌ pip 层重建(同上) |
8. docker build 常用参数
| 参数 | 作用 | 示例 |
|---|---|---|
-t |
镜像名称:标签 | -t myapp:1.0 |
-f |
指定 Dockerfile 路径 | -f Dockerfile.prod . |
--build-arg |
传递构建参数 | --build-arg VERSION=2.0 |
--no-cache |
不使用缓存 | --no-cache |
--target |
多阶段构建到指定阶段 | --target builder |
--platform |
指定目标平台 | --platform linux/arm64 |
▶ 示例:构建镜像并查看层历史(难度⭐⭐)
BASH
# Build with tag
docker build -t myapp:1.0 .
# View image layers
docker history myapp:1.0
9. 完整示例:为 Flask 应用编写 Dockerfile
DOCKERFILE
# ============================================
# Dockerfile for a Flask web application
# Demonstrates: FROM, WORKDIR, COPY, RUN, CMD
# ============================================
# Use official Python slim image
FROM python:3.12-slim
# Set working directory inside container
WORKDIR /app
# Copy dependency file first (cache optimization)
COPY requirements.txt .
# Install dependencies (clean cache in same layer)
RUN pip install --no-cache-dir -r requirements.txt
# Copy application source code
COPY . .
# Expose the application port (documentation only)
EXPOSE 5000
# Run the Flask application
CMD ["python", "app.py"]
BASH
# Build the image
docker build -t flask-app:1.0 .
# Run the container
docker run -d -p 5000:5000 --name my-flask flask-app:1.0
# Test the application
curl http://localhost:5000
# View image size and layers
docker images flask-app
docker history flask-app:1.0
💻 输出(节选):
TEXT
📖 仅展示
# docker images flask-app
REPOSITORY TAG IMAGE ID SIZE
flask-app 1.0 a1b2c3d4e5f6 180MB
# docker history flask-app:1.0
IMAGE CREATED CREATED BY SIZE
a1b2c3d4e5f6 5 seconds ago CMD ["python" "app.py"] 0B
<missing> 5 seconds ago COPY . . 2.5kB
<missing> 5 seconds ago RUN pip install --no-cache-dir... 45MB
<missing> 5 seconds ago COPY requirements.txt . 58B
<missing> 5 seconds ago WORKDIR /app 0B
❓ 常见问题
Q 为什么要把 COPY 依赖文件放前面而 COPY 源码放后面?
A 利用层缓存机制。依赖变更频率远低于代码变更。先 COPY 依赖文件并安装,这一层会被缓存;后续只改代码时,依赖层直接用缓存,构建速度从分钟级降到秒级。如果先 COPY 所有文件,任何文件变化都导致 pip install 层缓存失效。
Q CMD 和 ENTRYPOINT 能一起用吗?
A 能,这是推荐模式。ENTRYPOINT 定义固定执行程序(如 python app.py),CMD 提供默认参数(如 --port 5000)。docker run 传入的参数会替换 CMD 而不是 ENTRYPOINT,实现"固定程序+灵活参数"。
Q 构建上下文是什么意思?
A docker build 最后的
. 指定构建上下文目录。Docker Client 将这个目录的所有文件打包发送给 Daemon。Dockerfile 中的 COPY/ADD 只能引用上下文内的文件。用 .dockerignore 排除不需要的文件,加速构建并减小上下文体积。Q .dockerignore 怎么写?
A 语法与 .gitignore 相同。必须排除:node_modules、.git、pycache
Q 构建失败怎么排查?
A 三步排查:① 看错误信息——Docker 会指出失败的指令和行号;② 检查上下文——确认 COPY 的文件存在;③ 交互调试——
docker run -it <last-successful-layer> bash 进入最后一层成功的镜像手动排查。Q RUN 写多行太长怎么办?
A 用
\ 续行,&& 链接命令。这是 Dockerfile 的标准写法,既减少层数又保持可读性。例如:RUN apt-get update && \ + apt-get install -y nginx && \ + rm -rf /var/lib/apt/lists/*。📖 小节
- Dockerfile 是镜像的"源代码",每条指令生成一个只读层
- FROM 选择基础镜像:slim 兼容性好,alpine 最小但可能兼容性差
- RUN 合并命令减少层数,同层内清理缓存减小体积
- CMD 提供默认命令(可覆盖),ENTRYPOINT 定义固定入口(难覆盖)
- ENTRYPOINT + CMD 搭配模式是最佳实践:固定程序 + 灵活参数
- COPY 依赖文件先于源码,利用层缓存加速构建
📝 作业
- 基础题(难度⭐):为一个 Node.js 应用编写一个 Dockerfile,使用
node:20-alpine作为基础镜像,npm install安装依赖,node server.js启动应用。 - 进阶题(难度⭐⭐):构建镜像并运行容器,用
docker history分析镜像的每一层大小,找到最大的一层并解释原因。 - 挑战题(难度⭐⭐⭐):创建 .dockerignore 文件排除 node_modules 和 .git,对比有/无 .dockerignore 时构建上下文的大小差异(提示:
docker build输出中的 "Sending build context to Docker daemon" 行)。