Docker 入门:从镜像到容器

理解镜像、容器、Dockerfile 和层缓存,从零开始交互式学习

Docker 架构概念图

Docker 的核心思想很简单:把应用和它的所有依赖打包成一个可移植的单元。点击下方的框,了解每个组件的关系。

Docker Client
docker build / run / ps
Docker Daemon
dockerd 守护进程
Registry
Docker Hub / 私有仓库
Dockerfile
构建镜像的配方
Image (镜像)
只读模板
Container (容器)
运行的实例
核心关系: Dockerfile 是配方,镜像是按配方构建的只读模板,容器是从镜像启动的运行实例。一个镜像可以创建无数个容器,就像一个类可以 new 出无数个对象。

关键术语树

Dockerfile 逐行解析

这是一个典型的 Python Web 应用 Dockerfile。点击左侧的每一行,右侧会显示详细解释。

点击左侧代码行查看解释

每一行 Dockerfile 指令都会生成一个镜像层,理解每个指令的含义是写好 Dockerfile 的基础。

层缓存模拟器

Docker 构建时,每个指令产生一个层。如果输入没变,层会被缓存复用。一旦某层失效,它后面所有层都得重建。试试调整顺序,看看缓存如何变化。

修改了什么文件?

Docker 构建从上到下依次执行指令。如果第 N 层的输入发生了变化,那么第 N 层及其后面所有层都会失效,需要重新构建。

试试这样:把 COPY . . 移到 RUN pip install 前面,然后选择"源代码变化"。你会发现 pip install 层也变红了——意味着每次改代码都要重新安装依赖,非常慢。

所以原则是:先复制不常变的文件(如 requirements.txt),再复制常变的源代码

容器生命周期

点击 Run 观察容器从构建到运行到清理的全过程。每个阶段对应一条 docker 命令。

docker build
docker run
docker exec
docker stop
docker rm
关键区别: docker stop 只是停止容器,容器仍然存在,可以用 docker start 重新启动。而 docker rm 才会真正删除容器。镜像在整个过程中不会被删除,需要单独用 docker rmi

Level 1 — 认知与探索

Recognition
⏱ ~10 min🎯 阅读 Dockerfile,理解每条指令的作用

你已经在上方看到了一个完整的 Dockerfile。现在来检验一下理解:下面的命令匹配练习中,将左侧的 Dockerfile 指令与右侧的描述正确匹配。使用上下按钮调整顺序。

指令匹配练习

Q:"设置容器内的工作目录" 对应哪条指令?点击下方按钮查看答案。

WORKDIR /app — 设置后续指令的工作目录。如果目录不存在会自动创建。

其他指令对应:

FROM = 选择基础镜像
COPY = 复制文件到容器
RUN = 构建时执行命令
EXPOSE = 声明监听端口
CMD = 容器启动时执行的命令

验收标准: 能说出 FROM、COPY、RUN、CMD 各自的作用,并理解"镜像是只读模板,容器是运行实例"这一关系。

回到上方的 Dockerfile 解析区,点击每一行复习一重。注意指令的执行顺序:FROM 永远在第一行,CMD 通常在最后。

为什么重要: Dockerfile 就是一个构建脚本,只是它构建的产物是可移植的镜像。理解每条指令的含义后,你就能读懂任何 Dockerfile。

Level 2 — 补全 Dockerfile

Guided Practice
⏱ ~15 min🎯 根据提示填写缺失的 Dockerfile 指令

一个 Node.js Express 应用需要容器化。Dockerfile 中有 5 处 TODO,请补全。点击 Check 检查是否正确。

Dockerfile
验收标准: 7 项检查全部通过(FROM、WORKDIR、COPY package、RUN npm install、COPY、EXPOSE、CMD)。

参考上方解析的 Python Dockerfile,结构类似:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]

注意:先复制 package.json 再安装依赖,最后才复制源代码——这是为了利用层缓存。

为什么重要: 手写 Dockerfile 是容器化的第一步。掌握这 7 条指令后,90% 的应用都可以用这个模板容器化。

Level 3 — 独立编写

Independent
⏱ ~20 min🎯 从零写一个完整的 Dockerfile

从零写一个 Python Flask 应用的 Dockerfile。要求:

Dockerfile
验收标准: 8 项检查全部通过,包括指令顺序正确、使用了 --no-cache-dir、CMD 用了 JSON 数组形式、包含注释。

CMD 的 JSON 数组形式:CMD ["python", "app.py"]。这比 shell 形式 CMD python app.py 更推荐,因为它不经过 shell 处理,信号可以直接传递给进程。

--no-cache-dir 告诉 pip 不要在容器内保存缓存,可以减小镜像体积。

为什么重要: 从零编写是巩固理解的最佳方式。如果你能独立完成这个任务,说明你已经掌握了 Dockerfile 的核心结构。

Level 4 — 优化挑战

Challenge
⏱ ~25 min🎯 优化 Dockerfile,最大化层缓存命中率

下面是一个"笨拙"的 Dockerfile。它能跑但层缓存很差。请用上方的层缓存模拟器测试你的优化方案,记录你的思考过程。

待优化的 Dockerfile:
FROM python:3.12
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]

问题:

优化后的 Dockerfile
验收标准: 优化后的 Dockerfile 应将 COPY requirements.txt 和 RUN pip install 放在 COPY . . 之前,使用 slim 基础镜像,添加 --no-cache-dir。在模拟器中"源代码变化"时应只有 2 层重建。

原始 Dockerfile 的 3 个问题:

1. 使用了 python:3.12 完整镜像(~900MB)而非 slim(~45MB)
2. COPY . .pip install 之前,每次源代码变化都会重新安装依赖
3. pip install 没有 --no-cache-dir,会在镜像中留下缓存

优化后:

# 使用 slim 减小体积
FROM python:3.12-slim
WORKDIR /app
# 先复制依赖文件,利用层缓存
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 再复制源代码
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

为什么重要: 在实际工程中,层缓存的优化可以把构建时间从几分钟缩短到几秒。这个"先复制依赖再复制源码"的模式适用于 Node.js、Go、Java 等所有语言。

自测

检验你对 Docker 核心概念的理解。共 8 题,每题选完后查看解析。

容器 vs 虚拟机

经常有人问"容器和虚拟机有什么区别"。这是理解 Docker 价值的关键。

虚拟机 (VM)Docker 容器
隔离级别硬件级(虚拟 CPU/内存)进程级(共享内核)
启动时间分钟级秒级
资源占用每个 VM GB 级内存每个容器 MB 级
包含内核是(完整 OS)否(共享宿主内核)
密度低(数十个)高(数百个)
安全隔离强(硬件隔离)较弱(内核共享)
类比: VM 像把一栋楼拆成多个独立公寓,每户有自己的地基和管线。容器像一栋楼里的多个办公室,共享地基和管线但有自己的门和墙。

术语表

点击卡片翻转查看定义。

Image 镜像

只读的模板,包含运行应用所需的所有文件和配置。由 Dockerfile 构建,由多个层组成。

Container 容器

镜像的运行实例。可以启动、停止、删除。一个镜像可以创建无数个容器。

Layer 层

Dockerfile 中每条指令产生的只读文件系统层。相同的层会被缓存复用,加速构建。

Dockerfile

构建镜像的纯文本配方。从 FROM 开始,包含 COPY、RUN、CMD 等指令。

Volume 卷

独立于容器生命周期的持久化存储。容器删除后数据不丢失。

Registry 仓库

存储和分发镜像的服务。Docker Hub 是官方公共仓库,也可以自建私有仓库。

Tag 标签

镜像的版本号。如 python:3.12-slim 中的 3.12-slim 就是 tag。不指定时默认 latest。

Build Context

docker build 时传入的路径。Docker 会将该目录下所有文件发送给 daemon,用于 COPY 指令。

总结与下一步

核心要点

  • Dockerfile 是配方,镜像是模板,容器是运行实例
  • 每条 Dockerfile 指令产生一个层
  • 层缓存:输入不变则复用,变化则重建该层及其后所有层
  • 先复制依赖文件,再复制源代码,最大化缓存
  • 用 slim/alpine 基础镜像减小体积
  • CMD 用 JSON 数组形式优于 shell 形式

下一步学习

  • Docker Compose — 定义和运行多容器应用
  • Docker Volumes — 持久化数据存储的深入理解
  • Docker Networking — 容器间通信和端口映射
  • 多阶段构建 (multi-stage build) — 进一步减小镜像
  • Docker Hub — 发布和共享镜像
  • .dockerignore — 排除不需要的文件

实践建议

  • 安装 Docker Desktop,跑通今天的例子
  • 用你自己的项目写一个 Dockerfile
  • 对比 python:3.12 和 python:3.12-slim 的镜像大小
  • 尝试 docker exec -it 进入容器内部查看文件
  • 结合 Docker Compose 运行一个 Web + DB 的完整栈