4. Docker Compose编排:服务定义、网络配置、卷挂载、环境变量管理

说实话,Docker Compose 是我日常工作中用得最多的工具之一。你想想看,一个微服务项目动辄十几个服务,要是每个都手动 docker run,那不得累死?Compose 就是来解决这个问题的——用一个 YAML 文件,把整个应用栈描述清楚。

我个人习惯把 Compose 文件叫做「项目的说明书」。谁依赖谁、端口怎么映射、数据存哪里、环境变量配什么,全写在一个文件里。团队里新同学来了,看一眼 docker-compose.yml,基本就知道系统长什么样了。

4.1 服务定义:把每个容器说清楚

服务定义是 Compose 的核心。说白了,就是告诉 Docker:「我要跑哪些容器,每个容器用什么镜像、开什么端口、挂什么卷。」

来看一个我项目里用过的例子:

version: '3.8'

services:
  web:
    image: nginx:1.25-alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - api

  api:
    build: ./backend
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
      - DB_PORT=5432
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:15
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

这里有几个关键点:

  • image vs build:直接用现成镜像就用 image,需要自己构建就用 build。我建议开发阶段用 build,生产环境用 image 加 tag 锁定版本。
  • depends_on:控制启动顺序。但注意,它只保证容器启动了,不保证服务就绪了。所以我加了 healthcheck,让 api 等 db 真正健康了再启动。
  • ports:左边是宿主机端口,右边是容器端口。记得别冲突,我见过好几次 8080 被占用的惨案。
我的小技巧:服务名就是容器间的 DNS 名称。比如上面 api 服务里配的 DB_HOST=db,Compose 会自动解析到 db 容器的 IP。不用写死 IP,省心。

4.2 网络配置:容器间怎么通信

默认情况下,Compose 会为项目创建一个网络,所有服务都在这个网络里。但有时候我们需要更精细的控制。

为什么会这样?因为有些服务需要隔离,有些需要对外暴露。比如数据库通常只在内部网络访问,而 Nginx 需要暴露到外网。

我在项目中遇到过这样的场景:前端和后端需要通信,但数据库只对后端开放。我的做法是定义两个网络:

services:
  frontend:
    image: nginx
    networks:
      - frontend
      - backend

  backend:
    image: my-api
    networks:
      - backend

  database:
    image: postgres
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

这样 frontend 能访问 backend,但碰不到 database。backend 和 database 在同一个 backend 网络里,可以互相通信。

注意:网络驱动默认是 bridge。如果你用 Swarm 或 Kubernetes,可能需要 overlay 驱动。别搞混了,否则跨主机通信会出问题。

4.3 卷挂载:数据不能丢

容器是无状态的,重启后数据就没了。所以数据库、日志、上传文件这些,必须挂载到宿主机或卷里。

卷挂载有两种方式:

类型 写法 适用场景
绑定挂载 ./data:/app/data 开发调试,直接改宿主机文件
命名卷 myvolume:/app/data 生产环境,由 Docker 管理

我个人习惯开发时用绑定挂载,改代码立竿见影。生产环境用命名卷,方便备份和迁移。

来看一个完整的卷配置:

services:
  app:
    image: my-app
    volumes:
      - ./src:/app/src          # 开发时热更新
      - uploads:/app/uploads    # 上传文件持久化
      - /etc/localtime:/etc/localtime:ro  # 时区同步

volumes:
  uploads:
    driver: local

嗯,这里要注意:绑定挂载的路径必须是绝对路径或相对路径(以 ./ 或 ../ 开头)。命名卷需要在文件底部声明,否则 Compose 会报错。

避坑指南:我曾经在生产环境用了绑定挂载,结果宿主机磁盘满了,容器直接挂掉。后来全改成命名卷,配合定期备份,再没出过事。

4.4 环境变量管理:配置与代码分离

12-Factor App 告诉我们,配置要存到环境变量里。Compose 支持多种方式管理环境变量:

  1. 直接写在文件里:简单粗暴,适合小项目
  2. 使用 .env 文件:把变量抽出来,Compose 自动加载
  3. 使用 env_file:指定外部文件,适合多环境

来看一个实际项目的配置:

services:
  api:
    image: my-api
    environment:
      - NODE_ENV=production
      - DB_HOST=${DB_HOST}
      - DB_PORT=${DB_PORT}
      - REDIS_URL=${REDIS_URL}
    env_file:
      - ./config/${ENV}/db.env
      - ./config/${ENV}/redis.env

对应的 .env 文件:

DB_HOST=localhost
DB_PORT=5432
REDIS_URL=redis://localhost:6379
ENV=dev
我的建议:敏感信息(密码、密钥)不要写在 Compose 文件里。用 Docker Secrets 或外部密钥管理服务。我踩过这个坑——代码不小心提交到 Git,密钥全暴露了。

4.5 知识体系总览

下面这张图是我自己总结的 Compose 编排核心逻辑,你看一眼就能明白各个模块的关系:

Docker Compose 编排核心结构 docker-compose.yml 服务定义 网络配置 卷挂载 环境变量管理 image / build ports depends_on bridge / overlay 网络隔离 DNS 解析 绑定挂载 命名卷 数据持久化 .env 文件 env_file Docker Secrets 核心目标:一次编排,到处运行

这张图把四个核心模块串起来了。你写 Compose 文件时,就按这个思路来:先定义服务,再配网络,然后挂数据,最后管配置。顺序对了,基本不会出大问题。

总结一下:Docker Compose 不是银弹,但它能解决 80% 的本地开发和测试场景。把服务定义、网络、卷、环境变量这四个点吃透,你就能写出健壮的编排文件。剩下的 20%,等遇到再学也不迟。

公众号:蓝海资料掘金营,微信deep3321