Docker: Dockerfile基础

最后更新:2026-08-26

Dockerfile 是镜像的"源代码"——写好 Dockerfile,就能一键构建可复现的应用镜像。

1. 你将学到


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 是一个纯文本文件,包含构建镜像的指令序列。每条指令构建一个镜像层。

100%
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.22alpine 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。

100%
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 依赖文件先于源码,利用层缓存加速构建

📝 作业

  1. 基础题(难度⭐):为一个 Node.js 应用编写一个 Dockerfile,使用 node:20-alpine 作为基础镜像,npm install 安装依赖,node server.js 启动应用。
  2. 进阶题(难度⭐⭐):构建镜像并运行容器,用 docker history 分析镜像的每一层大小,找到最大的一层并解释原因。
  3. 挑战题(难度⭐⭐⭐):创建 .dockerignore 文件排除 node_modules 和 .git,对比有/无 .dockerignore 时构建上下文的大小差异(提示:docker build 输出中的 "Sending build context to Docker daemon" 行)。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏