Skip to content

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)就是一层。 构建时层会缓存——上层没变,下面已有的层直接复用。这就是"把不常变的放前面"能极大加速构建的原因。

text
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:五个必会指令 ​

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) ​

text
__pycache__/
*.pyc
.git/
.env
venv/
.venv/
node_modules/
dist/

不写 .dockerignore,COPY . . 会把整个目录(含 .git、依赖)塞进镜像。这是镜像臃肿的头号原因。

3.3 多阶段构建(用在哪) ​

当构建需要"重的工具"但运行不需要时(如 Node 前端打包、编译型语言),用多阶段:

dockerfile
# 阶段 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)**把数据挂到宿主机:

yaml
services:
  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data   # 命名卷,数据持久化
volumes:
  pgdata:

4.2 端口与网络 ​

概念说明前端类比
EXPOSE声明容器监听哪个端口文档里的"该端口提供服务"
-p 宿主机:容器真正把容器端口映射出来端口转发
容器网络同 Compose 内用服务名互访内网域名
bash
docker run -p 8000:8000 my-api   # 宿主机 8000 → 容器 8000

容器间通信不要用 localhost(那指容器自己),Compose 内直接服务名:http://db:5432。


五、Compose:一键起整套 ​

docker-compose.yml 描述"一个项目有哪些服务",类比项目的 README 里的启动说明,但可执行:

yaml
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:
bash
docker compose up -d      # 后台一键起整套
docker compose logs -f    # 看所有服务日志
docker compose down       # 停掉整套(加 -v 连数据卷一起删)

环境变量:Compose 里 environment 直接写或引用 .env,密钥不要写死在 yml 里(见 C12 错误处理日志配置)。


六、生产要补的几件事 ​

事项为什么
非 root 运行容器内默认 root,被攻破=宿主机权限高
健康检查让编排系统知道"何时算活着"
日志打到 stdout容器日志由编排系统收集,别写文件
资源限制不给限制,一个容器能吃光宿主机内存
密钥走环境变量/密钥服务镜像会被拉取分享,密钥进去=裸奔
dockerfile
RUN useradd -m appuser          # 建非 root 用户
USER appuser                    # 切换
yaml
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 整套可用。


相关文档 ​

本区 ​

其他域 ​