Docker 进阶实战:Dockerfile、网络、数据卷与 Compose
本篇目标:把自己的应用容器化。覆盖从写 Dockerfile 到多容器编排的完整链路,例子以一个 FastAPI + PostgreSQL 的 Web 应用为主线(FastBlog 项目同款架构)。
一、Dockerfile:把应用打成镜像
Dockerfile 是镜像的"配方",逐条指令构建分层:
FROM python:3.11-slim # 基础镜像(slim 变体更小)
WORKDIR /app # 后续指令的工作目录
COPY requirements.txt . # 先只拷依赖清单!
RUN pip install --no-cache-dir -r requirements.txt \
-i https://mirrors.ustc.edu.cn/pypi/simple
COPY . . # 再拷业务代码(改动频繁)
EXPOSE 8000 # 声明端口(文档作用)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
docker build -t myapp:1.0 .
docker run -d -p 8000:8000 myapp:1.0
分层缓存:构建速度的关键
每条指令生成一层,某层的输入没变,重建时直接用缓存。上面的写法故意"先 COPY requirements 再 COPY 代码":改代码时依赖层缓存依然命中,无需重装依赖。如果反过来一行 COPY . . 全量拷贝,改一个字符就要重新 pip install——这是新手最常犯的构建性能错误。
常用指令速查
| 指令 | 说明 | 要点 |
|---|---|---|
| RUN | 构建期执行命令 | 同类操作用 && 合并成一层,减少层数与体积 |
| COPY / ADD | 拷贝文件 | 优先 COPY(ADD 有解压等隐式行为) |
| ENV | 环境变量 | 构建期与运行期都可见 |
| ARG | 构建参数 | 仅构建期,--build-arg 传入 |
| ENTRYPOINT / CMD | 启动命令 | ENTRYPOINT 固定主程序,CMD 提供默认参数 |
| VOLUME | 声明挂载点 | 匿名卷声明 |
二、容器网络:四种模式
docker network ls # 查看网络
docker run --network host ... # 指定模式
| 模式 | 说明 | 场景 |
|---|---|---|
| bridge(默认) | 独立网段,通过端口映射对外 | 绝大多数场景 |
| host | 共享宿主机网络栈,无隔离 | 追求极致性能 |
| none | 无网络 | 离线任务/安全隔离 |
| 自定义 bridge | 容器间可用容器名互相访问 | 多容器协作(重点) |
自定义网络是多容器通信的正解:
docker network create appnet
docker run -d --name db --network appnet postgres:15
docker run -d --name app --network appnet -e DB_HOST=db myapp:1.0
# ↑ 容器内直接用容器名 db 当主机名,自带 DNS 解析
三、数据卷:让数据活过容器生命周期
# 命名卷:docker 管理,推荐用于数据库数据
docker volume create pgdata
docker run -d --name db -v pgdata:/var/lib/postgresql/data postgres:15
# 绑定挂载:宿主机目录直通容器,推荐用于配置文件、开发热更新
docker run -d -v /opt/myapp/conf:/app/conf:ro myapp:1.0
# │ro 只读
选型口诀:数据库数据用命名卷(可移植、好备份);配置和代码用绑定挂载(直观可控)。删除容器 docker rm 不会删命名卷,需 docker rm -v 或 docker volume rm,这是很多人"删了容器数据还在/没了"困惑的来源。
四、Docker Compose:多容器编排
真实应用从来不止一个容器。Compose 用一个 YAML 描述整套应用:
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # 从 .env 读取,别硬编码
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck: # 健康检查,配合 depends_on
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 10
restart: unless-stopped
backend:
build: . # 用 Dockerfile 构建
environment:
DB_HOST: db # 直接用服务名当主机名
depends_on:
db:
condition: service_healthy # 等 db 健康再启动
restart: unless-stopped
web:
image: nginx:latest
ports: ["80:80"]
volumes:
- ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on: [backend]
volumes:
pgdata:
docker compose up -d # 一键起全套
docker compose logs -f backend # 看某服务日志
docker compose ps # 整套状态
docker compose down # 停止并删除(加 -v 连数据卷一起删,高危!)
docker compose up -d --build # 改了代码重建镜像再启动
depends_on + healthcheck 的组合解决了"后端先于数据库启动导致连库失败"的经典问题,务必掌握。.env 文件与 compose 同目录时自动加载,密钥类配置全部走 .env 并加入 .gitignore。
五、动手练习
- 给自己的任意项目写 Dockerfile,用
time docker build对比"先 COPY 依赖清单"和"全量 COPY"两种写法的二次构建耗时。 - 复刻本文的 Compose 例子,故意把 healthcheck 删掉重启,观察 backend 首次启动的连库报错,理解 condition 的价值。
- 用
docker volume和docker cp两种方式把容器里的文件拿到宿主机。
下一篇预告
镜像构建好了、编排跑起来了,生产环境还有一堆现实问题:镜像太大、容器占资源失控、以 root 运行的安全隐患、日志把磁盘写满。最后一篇《Docker 精通之路》逐一击破。
我的恒园天地