01 · Docker 与容器化:从镜像到 Compose
前端的"依赖地狱"是
node_modules;后端的依赖地狱是整个运行环境。Docker 把"运行环境"打包成镜像,让本地、测试、生产完全一致。本篇用前端类比讲清容器、镜像、Compose,并给一条能立刻上手的动手路径。
速览卡
| 项 | 内容 |
|---|---|
| 一句话定位 | 容器是什么、镜像怎么构建、多服务怎么编排,以及"什么时候该用、什么时候别用" |
| 核心内容 | 镜像 vs 容器 vs 层、Dockerfile 五指令、镜像构建优化、数据卷与网络、Compose 编排、生产注意事项 |
| 读完能回答 | Docker 到底解决了什么?Dockerfile 怎么写才能又快又小?多个服务怎么一键起? |
| 相关板块 | C17 · 部署与 CI/CD | D02 · 部署与 CI/CD | C09 · 服务端语言上手 |
一、先建立三个心智模型
1.1 镜像 ≈ 一个"装好环境的文件系统快照"
前端类比:镜像就像你打好包的 App(.ipa/.apk)——它是不可变的,谁拿到都能跑,不依赖本机环境。 容器 = 镜像运行起来的实例,类比"App 在手机上启动"。
| 概念 | 前端类比 |
|---|---|
| Dockerfile | 打包脚本(≈ 构建 build 的配置文件) |
| 镜像(Image) | 构建产物(≈ dist/ / .apk) |
| 容器(Container) | 运行中的进程实例(≈ 打开的 App) |
| 镜像仓库(Registry) | 包仓库(≈ npm registry / 应用商店) |
| Compose | 一键启动整套服务的编排文件(≈ docker-compose = 项目启动脚本) |
1.2 层(Layer)与缓存
镜像由一层层只读文件系统堆叠而成。每条能产生文件变化的指令(RUN/COPY/ADD)就是一层。 构建时层会缓存——上层没变,下面已有的层直接复用。这就是"把不常变的放前面"能极大加速构建的原因。
Dockerfile 指令顺序 层缓存命中情况
--------------------------------------------
FROM python:3.12-slim ✅ 基础镜像缓存
WORKDIR /app ✅ 不变
COPY requirements.txt . ✅ 依赖列表没变 → 命中
RUN pip install -r ... ✅ 依赖没变 → 跳过重装(最耗时的一层!)
COPY . . ❌ 代码变了 → 这一层起重建
CMD ["uvicorn", "main:app", ...]一个最常见的错误:把
COPY . .写在前面,导致每次改一行代码都重装一遍依赖。依赖先 COPY,代码后 COPY。
1.3 容器 vs 虚拟机
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 隔离 | 完整操作系统,隔离强 | 共享宿主机内核,隔离轻 |
| 启动 | 秒~分钟级 | 毫秒~秒级 |
| 体积 | GB 级 | MB 级 |
| 类比 | 一台完整的电脑 | 一个"进程+环境"的沙箱 |
容器不是"轻量虚拟机"。它共享内核,所以不能在容器里跑"另一个操作系统",但隔离性对绝大多数服务足够。
二、Dockerfile:五个必会指令
# 1. 基础镜像(FROM 必须第一行,越精简越好)
FROM python:3.12-slim
# 2. 工作目录(WORKDIR 相当于 cd,但没有就自动创建)
WORKDIR /app
# 3. 拷贝 + 安装依赖(先依赖后代码,利用层缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. 拷贝代码
COPY . .
# 5. 容器启动时执行什么(CMD 可被 docker run 覆盖;每个镜像一般只有一个)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]| 指令 | 作用 | 前端类比 |
|---|---|---|
FROM | 指定基础镜像 | 项目依赖的 node 版本 |
WORKDIR | 设置工作目录 | cd 进项目根 |
COPY / ADD | 拷贝文件进镜像 | 把源码打进包 |
RUN | 构建时执行命令 | npm install、npm run build |
CMD / ENTRYPOINT | 运行时执行命令 | 启动命令 |
RUN是构建时,CMD是运行时——这是前端最容易混的点。RUN把结果写进镜像(装依赖、跑构建),CMD只是记录"容器启动后做什么"。
三、让镜像又快又小
3.1 三条黄金规则
| 规则 | 为什么 |
|---|---|
| 依赖先 COPY | 利用层缓存,代码改动不触发重装 |
用 .dockerignore | 排除 node_modules/.git/__pycache__,避免塞进镜像还污染构建上下文 |
| 基础镜像选精简版 | python:3.12-slim(几百 MB)而不是 python:3.12(GB 级),生产别带编译器 |
3.2 .dockerignore(类比 .gitignore)
__pycache__/
*.pyc
.git/
.env
venv/
.venv/
node_modules/
dist/不写
.dockerignore,COPY . .会把整个目录(含.git、依赖)塞进镜像。这是镜像臃肿的头号原因。
3.3 多阶段构建(用在哪)
当构建需要"重的工具"但运行不需要时(如 Node 前端打包、编译型语言),用多阶段:
# 阶段 1:构建(带完整工具链)
FROM node:20 AS build
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
# 阶段 2:运行(只拷贝产物,镜像很小)
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80第一阶段装的东西全丢弃,最终镜像只有"构建产物 + 运行环境"。这是"体积优化"的默认答案,简单且效果立竿见影。
四、数据卷与网络
4.1 数据卷:容器死了,数据不能死
容器是临时的——重建容器默认丢数据(数据库、上传文件都存容器内)。用**数据卷(volume)**把数据挂到宿主机:
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data # 命名卷,数据持久化
volumes:
pgdata:4.2 端口与网络
| 概念 | 说明 | 前端类比 |
|---|---|---|
EXPOSE | 声明容器监听哪个端口 | 文档里的"该端口提供服务" |
-p 宿主机:容器 | 真正把容器端口映射出来 | 端口转发 |
| 容器网络 | 同 Compose 内用服务名互访 | 内网域名 |
docker run -p 8000:8000 my-api # 宿主机 8000 → 容器 8000容器间通信不要用
localhost(那指容器自己),Compose 内直接服务名:http://db:5432。
五、Compose:一键起整套
docker-compose.yml 描述"一个项目有哪些服务",类比项目的 README 里的启动说明,但可执行:
services:
api:
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgres://user:pass@db:5432/app
depends_on:
- db
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:docker compose up -d # 后台一键起整套
docker compose logs -f # 看所有服务日志
docker compose down # 停掉整套(加 -v 连数据卷一起删)环境变量:Compose 里
environment直接写或引用.env,密钥不要写死在 yml 里(见 C12 错误处理日志配置)。
六、生产要补的几件事
| 事项 | 为什么 |
|---|---|
| 非 root 运行 | 容器内默认 root,被攻破=宿主机权限高 |
| 健康检查 | 让编排系统知道"何时算活着" |
| 日志打到 stdout | 容器日志由编排系统收集,别写文件 |
| 资源限制 | 不给限制,一个容器能吃光宿主机内存 |
| 密钥走环境变量/密钥服务 | 镜像会被拉取分享,密钥进去=裸奔 |
RUN useradd -m appuser # 建非 root 用户
USER appuser # 切换services:
api:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
retries: 3自检清单
- [ ] 能解释镜像 / 容器 / 层 / 仓库四个概念,并用前端类比讲给别人听
- [ ] 能写出带"依赖先 COPY、代码后 COPY"的 Dockerfile
- [ ] 有
.dockerignore,.git/node_modules不会进镜像 - [ ] 会用一个多阶段构建示例做体积优化
- [ ] 会给有状态服务(DB/上传)挂数据卷
- [ ] 会用 Compose 一键起"API + 数据库"并理解服务名互访
- [ ] 知道生产要非 root、健康检查、日志打 stdout、密钥不写死
常见误区
| 误区 | 事实 |
|---|---|
| "容器 = 轻量虚拟机" | 共享宿主机内核,隔离轻、快,但不是完整系统 |
| "Dockerfile 顺序无所谓" | 顺序决定层缓存命中率,先依赖后代码能大幅加速 |
| "数据存容器里就行" | 容器重建就丢,持久数据必须用数据卷 |
| "Compose 里用 localhost 连数据库" | 容器间用服务名,localhost 指容器自己 |
| "生产也用默认 root" | 被攻破后权限过高,应建非 root 用户 |
| "日志写文件更稳" | 容器应打 stdout,交给编排系统收集 |
动手练习
练习 A:容器化一个 FastAPI 服务
给 C09 的 Todo API 写 Dockerfile,docker build + docker run -p 8000:8000 跑通,再补 .dockerignore 看镜像体积变化。
练习 B:拆两层看缓存
先 COPY . . 再 RUN pip install,构建两次(改一行代码);再把顺序调成"依赖先、代码后",对比第二次构建的耗时差异。
练习 C:Compose 起整套
写 docker-compose.yml 让 API + PostgreSQL 一起起,用 depends_on + 环境变量让 API 连上数据库,验证一次 docker compose up -d 整套可用。
相关文档
本区
- 02 · 部署与 CI/CD —— 镜像构建好之后怎么发布
- D 域 README —— 运维域索引
其他域
- C17 · 部署与 CI/CD(后端视角) —— 后端项目部署入门
- C09 · 服务端语言上手 —— 容器化的对象
- C12 · 错误处理日志配置 —— 多环境与密钥管理
- A16 · CI 与 CD 与部署运维 —— 前端发布流水线对照