Docker & Compose
实战指南

不讲历史不讲哲学,全是能跟着敲的命令、能跑起来的代码。从「容器是什么」到「多服务编排 + 生产优化」一条龙。

🟢 入门 🟡 进阶 🔴 高级 🐳 Compose

目录

1容器到底是什么入门

容器 = 把"应用 + 它依赖的所有东西"打成一个包,在任何能跑 Docker 的机器上一致运行。它不是虚拟机,没装一个完整的操作系统,更轻、更快。

flowchart TB subgraph VM[传统虚拟机 VM] H1[硬件 Hardware] H1 --> OS1[宿主 OS] OS1 --> HV[Hypervisor] HV --> G1[Guest OS
+ Bins/Libs
+ App A] HV --> G2[Guest OS
+ Bins/Libs
+ App B] end subgraph DK[Docker 容器] H2[硬件 Hardware] H2 --> OS2[宿主 OS
+ Linux Kernel] OS2 --> DE[Docker Engine] DE --> C1[Bins/Libs
+ App A] DE --> C2[Bins/Libs
+ App B] DE --> C3[Bins/Libs
+ App C] end classDef vm fill:#1a2233,stroke:#fbbf24,color:#fbbf24 classDef dk fill:#1a2233,stroke:#2496ed,color:#2496ed class H1,OS1,HV,G1,G2 vm class H2,OS2,DE,C1,C2,C3 dk

1.1 三个必须区分的概念

概念类比解释
Image 镜像蓝图 / class只读模板,由若干层(layer)叠加而成
Container 容器实例 / object镜像的一次运行,可启停删,可有状态
Registry 仓库App Store存镜像的地方,如 Docker Hub、GHCR、阿里云

1.2 为什么团队都在用

✅ 环境一致

「我这能跑」从此作废。开发、测试、生产同一个镜像。

✅ 启动秒级

容器没装 OS,几百毫秒就能起一个新实例。

✅ 隔离不重

用 Linux namespace + cgroup 隔离,比 VM 轻一个数量级。

2安装与验证入门

🍎 macOS / 🪟 Windows

Docker Desktop。带图形界面,自带 Compose、Kubernetes。

🐧 Linux (Ubuntu/Debian)

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER     # 免 sudo
newgrp docker

验证

docker --version            # Docker version 27.x.x
docker compose version      # Docker Compose version v2.x.x
docker run hello-world      # 拉镜像 → 跑容器 → 打印消息
注意: 老语法是 docker-compose(带横线,独立程序),新的是 docker compose(空格,作为 Docker CLI 插件)。本教程全部使用新语法。

3跑第一个容器入门

跑一个 nginx:

docker run -d -p 8080:80 --name my-nginx nginx:alpine

拆解这一行:

参数含义
run创建并启动容器
-ddetach,后台运行
-p 8080:80宿主机 8080 → 容器 80
--name my-nginx给容器起名(不写就随机)
nginx:alpine镜像名:标签(alpine 是更小的版本)

浏览器打开 http://localhost:8080 → 看到 Nginx 欢迎页 ✅

容器的生命周期

stateDiagram-v2 [*] --> Created : docker create Created --> Running : docker start [*] --> Running : docker run Running --> Paused : docker pause Paused --> Running : docker unpause Running --> Stopped : docker stop Stopped --> Running : docker start Stopped --> [*] : docker rm Running --> Stopped : 进程退出

4核心命令速查入门

4.1 容器管理

docker ps列出运行中的容器
docker ps -a列出所有容器(含已停止)
docker logs -f <name>查看日志,-f 跟随
docker exec -it <name> sh进入容器 shell
docker stop <name>优雅停止(先 SIGTERM 后 SIGKILL)
docker rm <name>删除已停止的容器
docker rm -f <name>强制删除(运行中也删)
docker stats实时 CPU/内存/IO 监控
docker inspect <name>查看完整 JSON 配置

4.2 镜像管理

docker images列出本地镜像
docker pull nginx:1.27拉取镜像
docker rmi <image>删除镜像
docker build -t myapp:1.0 .从 Dockerfile 构建
docker tag a:1 b:2打新标签
docker push myapp:1.0推到仓库
docker history <image>查看镜像分层历史

4.3 一键清理

docker system df              # 看占用
docker system prune -a        # 清除未使用的镜像/容器/网络
docker volume prune           # 清除未使用的卷(小心数据)

5镜像与 Dockerfile进阶

Dockerfile 是一份"如何把代码做成镜像"的说明书。每一条指令产生一个层(layer),层会被缓存,改一行只重做那一层之后的内容。

5.1 最常用指令

指令作用示例
FROM基础镜像FROM python:3.12-slim
WORKDIR工作目录(自动创建)WORKDIR /app
COPY从宿主复制到镜像COPY . .
RUN构建时执行命令RUN pip install -r req.txt
ENV设置环境变量ENV PORT=8000
EXPOSE声明端口(文档作用)EXPOSE 8000
CMD默认启动命令CMD ["python","app.py"]
ENTRYPOINT主入口(更"固定")ENTRYPOINT ["python"]

5.2 第一个真实例子:Python Flask 应用

# app.py
from flask import Flask
app = Flask(__name__)

@app.get("/")
def hello():
    return {"msg": "Hello from Docker!"}

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)
# Dockerfile
FROM python:3.12-slim

WORKDIR /app

# 关键:先 COPY requirements,单独一层 → 改代码不会重装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["python", "app.py"]
docker build -t flask-demo:1.0 .
docker run -d -p 8000:8000 --name web flask-demo:1.0
curl http://localhost:8000
层缓存的黄金法则: 变化频繁的指令(如 COPY . .)放后面,变化少的(依赖安装)放前面。否则每次改代码都要重装依赖,构建慢 10 倍。

5.3 .dockerignore(很多人忘)

类似 .gitignore,避免把无用文件打进镜像,加快构建:

.git
.env
node_modules
__pycache__
*.log
dist
.DS_Store

6数据卷与持久化进阶

容器删除后所有写入容器内的数据都会消失。要持久化必须用 volume。

6.1 三种挂载方式

flowchart LR H[宿主机] -.host path.-> M1[Bind Mount
开发常用] D[(Docker 管理)] -.named.-> M2[Named Volume
生产推荐] R[内存] -.tmpfs.-> M3[tmpfs
临时数据] M1 --> C[容器内路径] M2 --> C M3 --> C classDef m fill:#1a2233,stroke:#2496ed,color:#2496ed class M1,M2,M3 m
类型命令场景
Bind Mount-v $(pwd):/app开发:宿主代码映射进容器,改了立刻生效
Named Volume-v mydata:/var/lib/mysql生产:数据库持久化,Docker 管理
tmpfs--tmpfs /tmp敏感临时数据,只存内存

6.2 实战:MySQL 数据持久化

# 不持久化(数据随容器消失)
docker run -d --name db -e MYSQL_ROOT_PASSWORD=secret mysql:8

# 持久化到 named volume(推荐)
docker run -d --name db \
  -e MYSQL_ROOT_PASSWORD=secret \
  -v mysql_data:/var/lib/mysql \
  -p 3306:3306 \
  mysql:8

# 查看 volume
docker volume ls
docker volume inspect mysql_data

6.3 现代写法:--mount

语义更明确,强烈推荐用于脚本和生产:

docker run -d \
  --mount type=volume,source=mysql_data,target=/var/lib/mysql \
  --mount type=bind,source=$(pwd)/conf,target=/etc/mysql/conf.d,readonly \
  mysql:8

7网络模式进阶

flowchart LR subgraph Host[宿主机] direction TB subgraph br0[bridge - 默认] C1[容器 A] --- C2[容器 B] end subgraph custom[自定义 bridge ⭐] D1[web] --- D2[api] --- D3[db] Note[/同网络内可用
容器名互相访问/] end HN[host 模式
共享宿主网络栈] NN[none 模式
完全隔离] end Inet((Internet)) br0 --> Inet custom --> Inet HN --> Inet classDef good fill:#1a2233,stroke:#10b981,color:#10b981 class custom good

7.1 四种模式

模式特点用法
bridge (默认)每个容器一个 IP,不能用容器名互相访问不推荐
自定义 bridge内置 DNS,容器名 = 主机名必须用这个
host共享宿主网络栈,性能最好但无隔离性能敏感场景
none无网络纯计算/离线任务

7.2 实战:web 和 db 互通

# 1. 创建自定义网络
docker network create app-net

# 2. 容器挂到同一网络
docker run -d --name db --network app-net \
  -e POSTGRES_PASSWORD=pw postgres:16

docker run -d --name web --network app-net -p 8000:8000 \
  -e DATABASE_URL=postgres://postgres:pw@db:5432/postgres \
  myapp:1.0

# 在 web 容器里直接用 "db" 作为主机名连接 ✅
记一条最重要的: 默认 bridge 网络下容器名不能解析。永远用自定义 bridge 网络,这就是 Compose 默认做的事。

8镜像仓库进阶

# 登录
docker login                          # Docker Hub
docker login ghcr.io                  # GitHub Container Registry
docker login registry.gitlab.com      # GitLab Registry

# 推送(镜像名前缀 = 仓库地址)
docker tag myapp:1.0 ghcr.io/myorg/myapp:1.0
docker push ghcr.io/myorg/myapp:1.0

# 拉取私有镜像
docker pull ghcr.io/myorg/myapp:1.0

镜像命名规范

[registry/][namespace/]name[:tag]

例:
  nginx                           # Docker Hub library/nginx:latest
  python:3.12-slim                # 指定 tag
  ghcr.io/myorg/myapp:v1.2.3      # GHCR + 版本
  registry.cn-hangzhou.aliyuncs.com/myns/myapp:latest
避雷: 永远别用 :latest。它不是最新版,它是"上次有人 push 时打了 latest 的版本"。生产环境用具体 tag 或 SHA digest。

9镜像优化与多阶段构建高级

一个能跑的镜像 vs. 一个的镜像,差别在 3 个维度:体积构建速度安全表面

9.1 优化前后对比(真实案例:Node.js 应用)

❌ 朴素版 (1.2 GB)

FROM node:20
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

问题:基础镜像太大、装了 dev 依赖、源码+构建工具都进了最终镜像。

✅ 优化版 (180 MB)

# 阶段 1: builder
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段 2: runner(只要产物)
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]

体积 -85%,无 dev 依赖,非 root 用户。

9.2 多阶段构建原理

flowchart LR S1[阶段 1
builder
大镜像 + 构建工具] -->|只取产物| F[最终镜像] S2[阶段 2
tester
跑测试] -.可选.-> F S3[阶段 3
linter] -.可选.-> F F[最终镜像
小 + 干净] classDef build fill:#1a2233,stroke:#fbbf24,color:#fbbf24 classDef final fill:#1a2233,stroke:#10b981,color:#10b981 class S1,S2,S3 build class F final

9.3 通用瘦身技巧

选对基础镜像

  • alpine ~5MB,但 musl 偶有兼容问题
  • -slim ~80MB,glibc 兼容好(推荐)
  • distroless 无 shell,最安全
  • scratch 空镜像,仅 Go 静态二进制

合并 & 清理

RUN apt-get update \
 && apt-get install -y curl \
 && rm -rf /var/lib/apt/lists/*

关键:apt list、pip cache、npm cache 都要在同一个 RUN 层里删,否则层还在。

9.4 BuildKit 加速

现代 Docker 默认开启。可用 缓存挂载secret 挂载

# 缓存挂载:npm/pip cache 跨构建复用
RUN --mount=type=cache,target=/root/.npm \
    npm ci

# secret 挂载:构建期需要 token 但不留进镜像
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm install

10安全加固高级

🔒 7 条铁律

  1. 不用 rootUSER nodeUSER 1000
  2. 只读根文件系统--read-only + tmpfs 临时目录
  3. 限资源--memory=512m --cpus=1 防失控
  4. 不要 :latest — 用具体 tag 或 SHA digest
  5. secret 不写 Dockerfile — 用 BuildKit secret 或运行时注入
  6. 扫漏洞docker scout cves myapp:1.0 或 trivy
  7. 用最小基础镜像 — distroless / alpine
# 生产启动模板
docker run -d \
  --name web \
  --read-only \
  --tmpfs /tmp \
  --memory=512m --cpus=1 \
  --security-opt no-new-privileges \
  --cap-drop ALL \
  --user 1000:1000 \
  -p 8000:8000 \
  myapp@sha256:abc123...   # 用 digest 锁死版本

11调试与排障高级

11.1 容器起不来 / 一启动就退出

# 1. 看日志
docker logs my-container
docker logs --tail 100 -f my-container

# 2. 查退出原因
docker inspect my-container --format='{{.State.ExitCode}} {{.State.Error}}'

# 3. 改启动命令进去看
docker run -it --entrypoint sh myapp:1.0

11.2 进容器排查

# 进运行中的容器
docker exec -it web sh        # alpine
docker exec -it web bash      # debian/ubuntu

# 容器没装调试工具?用临时 sidecar
docker run -it --rm --pid=container:web \
  --net=container:web nicolaka/netshoot

11.3 资源/网络问题

docker stats实时 CPU/内存/网络
docker top <name>容器内进程列表
docker port <name>端口映射
docker network inspect <net>网络详情 + 谁连着
docker events实时事件流(创建/启停/OOM)

12Compose 起步 🐳Compose

单条 docker run 适合一个容器。但真实应用是 web + db + redis + worker + …,手敲命令既慢又错。Compose 用一个 YAML 文件描述整个应用栈,一条命令起停所有服务。

flowchart LR YAML[docker-compose.yml
声明式定义] --> Compose[docker compose up] Compose --> N[自动建网络] Compose --> V[自动建卷] Compose --> S1[启动 web] Compose --> S2[启动 db] Compose --> S3[启动 redis] S1 -.同一网络.- S2 S2 -.同一网络.- S3 classDef start fill:#1a2233,stroke:#2496ed,color:#2496ed class YAML,Compose start

12.1 最小例子

# compose.yaml
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
docker compose up -d              # 后台启动
docker compose ps                 # 查看
docker compose logs -f            # 看日志
docker compose down               # 停止 + 删容器(保留 volume)
docker compose down -v            # 连 volume 一起删
文件名: 官方推荐 compose.yaml(Compose Spec),老名字 docker-compose.yml 仍兼容。两者行为一致,新项目用前者。

13多服务实战:Web + API + DB + Redis 🐳Compose

下面是一份能直接跑的完整栈,覆盖几乎所有常用字段。

# compose.yaml
services:
  # --- 反向代理 ---
  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      api:
        condition: service_healthy
    restart: unless-stopped

  # --- 应用 API(用 Dockerfile 构建)---
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
      args:
        NODE_ENV: production
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
      REDIS_URL: redis://redis:6379
      NODE_ENV: production
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8000/health"]
      interval: 10s
      timeout: 3s
      retries: 5
      start_period: 20s
    deploy:
      resources:
        limits: { cpus: '1.0', memory: 512M }
    restart: unless-stopped

  # --- 数据库 ---
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      retries: 10
    restart: unless-stopped

  # --- 缓存 ---
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    restart: unless-stopped

volumes:
  db_data:
  redis_data:

13.1 配套 .env 文件

# .env (加进 .gitignore)
DB_PASSWORD=super-secret-pw-please-rotate
NODE_ENV=production

13.2 关键字段拆解

字段说明
build本地 Dockerfile 构建(可以和 image 共存:build 后用该 image tag)
environment / env_file注入环境变量;${VAR} 从宿主 / .env 读
depends_on启动顺序 + 健康检查依赖
healthcheck判断服务"真活着",影响 depends_on
volumes顶层声明卷 + 服务里挂载,:ro 只读
restartno / always / on-failure / unless-stopped
deploy.resourcesCPU/内存上限

13.3 常用命令

docker compose up -d启动全部,后台
docker compose up -d --build代码改了 → 强制重建镜像
docker compose ps所有服务状态
docker compose logs -f api看某个服务的日志
docker compose exec api sh进入某服务容器
docker compose restart api重启某服务
docker compose up -d --scale api=3横向扩容到 3 实例
docker compose config解析并展示最终配置(debug 利器)
docker compose down -v销毁全部 + 卷

14Compose 高级技巧高级

14.1 多环境:override + profile

Compose 默认会合并两个文件:compose.yaml + compose.override.yaml。后者覆盖前者。这是区分开发/生产的官方方式:

# compose.yaml(生产基准)
services:
  api:
    image: ghcr.io/myorg/api:1.2.0
    restart: unless-stopped

# compose.override.yaml(开发本地,自动加载)
services:
  api:
    build: ./api
    volumes:
      - ./api:/app     # 代码热更新
    environment:
      DEBUG: "true"
    ports:
      - "8000:8000"
docker compose up -d                                            # 开发
docker compose -f compose.yaml up -d                            # 只用 base(生产)
docker compose -f compose.yaml -f compose.prod.yaml up -d       # 显式合并

14.2 Profile:可选服务

services:
  api: { image: myapi }
  db:  { image: postgres }

  # 调试工具,平时不起
  adminer:
    image: adminer
    profiles: ["debug"]

  # 性能测试
  k6:
    image: grafana/k6
    profiles: ["perf"]
docker compose up -d                          # 只起 api + db
docker compose --profile debug up -d          # 加 adminer
docker compose --profile perf up -d k6        # 临时跑压测

14.3 复用配置:YAML anchor & extends

x-common-env: &common-env
  TZ: Asia/Shanghai
  LOG_LEVEL: info

services:
  api:
    image: myapi
    environment:
      <<: *common-env
      ROLE: api

  worker:
    image: myapi
    environment:
      <<: *common-env
      ROLE: worker
    command: ["python", "worker.py"]

14.4 自定义网络分层

services:
  nginx:
    image: nginx
    networks: [frontend]
  api:
    image: myapi
    networks: [frontend, backend]    # 双网卡
  db:
    image: postgres
    networks: [backend]               # 数据库只在内网

networks:
  frontend:
  backend:
    internal: true                    # 完全无外网

14.5 Secret 管理(生产)

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_pw
    secrets:
      - db_pw

secrets:
  db_pw:
    file: ./secrets/db_password.txt

14.6 监听 + 热更新(Compose Watch)

Compose 2.22+ 支持 watch,本地代码变化自动同步/重建:

services:
  api:
    build: ./api
    develop:
      watch:
        - action: sync           # 同步文件,无需重启
          path: ./api/src
          target: /app/src
        - action: rebuild        # package.json 变了 → 重建镜像
          path: ./api/package.json
docker compose up --watch

15走向生产高级

Compose 在单机生产是完全够用的(不少团队用它跑业务好几年)。要做好这几件事:

✅ 必做

  • 所有镜像用具体 tag / SHA
  • restart: unless-stopped
  • 每个服务有 healthcheck
  • deploy.resources.limits 防 OOM
  • 日志驱动:logging: { driver: json-file, options: { max-size: 10m, max-file: 3 } }
  • secret 走文件挂载,不写 env
  • 定期备份 volume

⚠️ 限制与升级路径

  • Compose 是单机编排;多机要上 Swarm / Kubernetes
  • 没有滚动更新、自动伸缩、亲和性调度
  • 用量增长后迁移目标:Kubernetes(用 kompose 工具能初步转换 yaml)

15.1 生产部署最小脚本

#!/usr/bin/env bash
set -euo pipefail
cd /opt/myapp

# 拉最新代码
git pull

# 用 .env.prod 启动
docker compose --env-file .env.prod \
  -f compose.yaml -f compose.prod.yaml \
  pull

docker compose --env-file .env.prod \
  -f compose.yaml -f compose.prod.yaml \
  up -d --remove-orphans

# 清理旧镜像
docker image prune -f

15.2 与 CI/CD 集成

flowchart LR A[git push] --> B[CI 构建镜像] B --> C[push 到 registry] C --> D[ssh 到生产机] D --> E[docker compose pull] E --> F[docker compose up -d] F --> G[健康检查] G --> H{OK?} H -->|是| I[✅ 完成] H -->|否| J[回滚到上版本 tag] classDef ok fill:#1a2233,stroke:#10b981,color:#10b981 classDef warn fill:#1a2233,stroke:#fbbf24,color:#fbbf24 class I ok class J warn

学习路径总结

flowchart TB S([开始]) --> L1[🟢 入门
跑通 docker run] L1 --> L2[🟢 入门
核心命令熟练] L2 --> L3[🟡 进阶
写 Dockerfile] L3 --> L4[🟡 进阶
volume + 自定义网络] L4 --> L5[🐳 Compose
多服务 yaml] L5 --> L6[🔴 高级
多阶段构建 + 优化] L6 --> L7[🔴 高级
安全 + 调试] L7 --> L8[🐳 Compose
多环境 + 生产] L8 --> K[🚀 Kubernetes
下一站] classDef basic fill:#1a2233,stroke:#10b981,color:#10b981 classDef inter fill:#1a2233,stroke:#fbbf24,color:#fbbf24 classDef adv fill:#1a2233,stroke:#ef4444,color:#ef4444 classDef compose fill:#1a2233,stroke:#2496ed,color:#2496ed classDef next fill:#1a2233,stroke:#a78bfa,color:#a78bfa class L1,L2 basic class L3,L4 inter class L5,L8 compose class L6,L7 adv class K next
记住一句话: Docker 让"应用 + 环境"成为一个不可变的产物;Compose 让"一组应用"也变得不可变。这是从"运维痛苦"到"运维优雅"的根本转变。