Skip to content

预计时间

1 周

学习目标

  • 编写多阶段 Dockerfile
  • 理解 Volume 持久化
  • 理解 Network 通信
  • 用 Compose 编排多服务

一、Docker 是什么?

text
问题:
  你:"我本地跑得好好的啊"
  运维:"服务器上就是不行"
  原因:Node 版本不同、系统库缺失、环境变量不一致

Docker 解决:
  把代码 + 运行时 + 系统库 + 环境变量 → 打包成一个镜像
  这个镜像在任何安装了 Docker 的机器上都能跑

核心概念:

text
Dockerfile   →  蓝本(源代码)
  ↓ build
Image        →  模板(类)
  ↓ run
Container    →  实例(对象)

类比:
  Dockerfile = 菜谱
  Image      = 菜(做好了随时可以上桌)
  Container  = 吃的过程(菜在盘子里,吃完就没了)

二、Dockerfile 详解

2.1 Next.js 前端

dockerfile
# ===== 构建阶段 =====
FROM node:20-alpine AS builder
WORKDIR /app

# 先 copy 依赖文件(利用缓存)
COPY package.json package-lock.json ./
RUN npm ci --silent

# 再 copy 源码
COPY . .

# 构建
RUN npm run build

# ===== 运行阶段 =====
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# 只 copy 必要的文件
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

EXPOSE 3000
CMD ["node", "server.js"]

多阶段构建的好处

构建阶段有 devDependencies、源代码、构建缓存 → 镜像很大(> 1GB) 运行阶段只 copy 最终产物 → 镜像小(< 200MB)

安全 + 快 + 省磁盘。

2.2 NestJS 后端

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --silent
COPY . .
RUN npm run build      # tsc 编译
RUN npm ci --silent --omit=dev  # 生产依赖

FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

EXPOSE 3001
CMD ["node", "dist/main"]

2.3 关键指令

指令作用注意事项
FROM基础镜像优先用 alpine(最小,5MB)
WORKDIR设置工作目录相当于 cd
COPY复制文件.dockerignore 排除 node_modules
RUN构建时执行每条 RUN 产生一层,合并用 &&
EXPOSE声明端口只是文档作用,不实际暴露
CMD容器启动命令只能有一个,格式 CMD ["executable","param1"]

三、Volume(持久化存储)

text
问题:
  Container 里的数据在容器删除后就没了。
  数据库跑在 container 里 → container 挂了 → 数据没了?

解决:Volume
  把宿主机的目录"挂载"到容器里
  容器删了,数据还在宿主机上

三种类型:

yaml
# 1. Named Volume(Docker 管理,最常用)
volumes:
  pgdata:              # Docker 自动创建和管理
services:
  postgres:
    volumes:
      - pgdata:/var/lib/postgresql/data

# 2. Bind Mount(指定宿主机路径,开发时用)
services:
  app:
    volumes:
      - ./src:/app/src  # 改代码实时生效

# 3. tmpfs(内存,重启就没了)
services:
  app:
    volumes:
      - type: tmpfs
        target: /app/cache

四、Network(网络)

text
默认情况:
  每个 container 在隔离网络中
  Container A 不能直接访问 Container B

Compose 自动创建网络:
  同一 compose 文件中的服务自动在同一网络中
  服务名就是 hostname!

  backend 可以这样连数据库:
  不用 IP,直接用服务名 → postgres://postgres:5432

网络模式:

yaml
# Bridge(默认)— 同一宿主机内通信
services:
  app:
    networks:
      - app-network
  db:
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

# Host — 直接使用宿主机网络(性能好,Linux only)
services:
  app:
    network_mode: host

五、Docker Compose 实战

完整 multi-service 编排

yaml
# docker-compose.yml
version: '3.8'

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - '3000:3000'
    environment:
      - NEXT_PUBLIC_API_URL=http://localhost:3001
    depends_on:
      backend:
        condition: service_healthy

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    ports:
      - '3001:3001'
    environment:
      - DATABASE_URL=postgresql://user:pass@postgres:5432/knowledge
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:3001/health']
      interval: 10s
      timeout: 5s
      retries: 3

  postgres:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=knowledge
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - '5432:5432'
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U user -d knowledge']
      interval: 5s
      timeout: 3s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - '6379:6379'
    volumes:
      - redisdata:/data
    command: redis-server --appendonly yes

  minio:  # S3 兼容对象存储(本地开发用)
    image: minio/minio
    ports:
      - '9000:9000'
      - '9001:9001'
    environment:
      - MINIO_ROOT_USER=minioadmin
      - MINIO_ROOT_PASSWORD=minioadmin
    volumes:
      - miniodata:/data
    command: server /data --console-address ":9001"

volumes:
  pgdata:
  redisdata:
  miniodata:
bash
# 启动所有服务
docker compose up -d

# 查看日志
docker compose logs -f backend

# 停止
docker compose down

# 停止并删除数据卷
docker compose down -v

.env 文件

bash
# .env(不要提交到 git)
DB_PASSWORD=your-secure-password
JWT_SECRET=your-jwt-secret
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin

六、常用命令速查

bash
# 镜像
docker images                          # 列出本地镜像
docker rmi <image>                     # 删除镜像
docker pull node:20-alpine             # 拉取镜像

# 容器
docker ps                              # 运行中的容器
docker ps -a                           # 包括已停止的
docker logs -f <container>             # 实时日志
docker exec -it <container> /bin/sh    # 进容器调试
docker rm <container>                  # 删除容器
docker rm -f <container>               # 强制删除

# Compose
docker compose up -d                   # 后台启动
docker compose down                    # 停止并删除
docker compose restart backend         # 重启单个服务
docker compose build --no-cache        # 重新构建

# 清理
docker system prune -a                 # 清理所有不用资源

实践

  1. 安装 Docker Desktop
  2. 把上面的 docker-compose.yml 跑起来
  3. 进容器看看:docker exec -it <backend-container> /bin/sh
  4. 改一行代码 → rebuild → 观察 layer caching 效果
  5. docker compose down -v → 再 up → 确认数据是否还在