Docker Compose 文件详解:服务、网络与卷

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我会在各个平台持续发布最新文章,助你少走弯路。

在第 11 篇中,我们第一次接触了 docker-compose.yml,体验了从"手动敲 8 条命令"到"一条 docker compose up -d 启动整个应用栈"的效率跃升。但当时我们只是"照葫芦画瓢"地写了一份配置文件------对每一行配置的含义、最佳实践和灵活用法还没有深入展开。

今天,我们就来彻底拆解 Compose 文件,把 services、networks、volumes 这三大核心模块吃透。掌握了这些,当你后面进入 Kubernetes 看到 Service、Ingress、ConfigMap、PersistentVolumeClaim 时,会发现它们的设计思路一脉相承------都是从"手动管理资源"到"声明式管理资源"的进化。

一、Compose 文件结构总览

一个完整的 Compose 文件由三个顶级元素构成:

bash 复制代码
services:    # 定义应用服务
  ...
networks:    # 定义网络
  ...
volumes:     # 定义数据卷
  ...

此外还有 configssecrets(用于 Swarm 模式,本篇暂不涉及)。我们重点解析前三个。

1.1 项目名称与资源命名

Compose 管理的所有资源(容器、网络、卷)都会自动加上项目名称作为前缀,避免不同项目之间命名冲突。默认的项目名称是 docker-compose.yml 所在目录的名称。例如目录名为 flask-redis-counter,那么:

  • 网络 app-net 实际创建为 flask-redis-counter_app-net

  • redis-data 实际创建为 flask-redis-counter_redis-data

  • 容器 redis 实际名称为 redis(因为显式指定了 container_name,如果未指定,则为 项目名-服务名-序号

你可以通过 -p 参数或 COMPOSE_PROJECT_NAME 环境变量自定义项目名称:

bash 复制代码
docker compose -p myproject up -d

二、services 详解:服务的核心配置

services 是 Compose 文件中最核心的模块,每个服务可以配置的选项非常多。我将按功能分类逐一拆解,并用我们贯穿的 Flask + Redis 应用来演示。

2.1 镜像与构建

指定服务使用的镜像有两种方式:

直接使用已有镜像:

bash 复制代码
services:
  redis:
    image: redis:alpine     # 从仓库拉取

从 Dockerfile 构建:

bash 复制代码
services:
  flask-app:
    build: .                 # 使用当前目录的 Dockerfile
    # 或者更详细的写法
    build:
      context: ./app         # 构建上下文路径
      dockerfile: Dockerfile.dev  # 指定 Dockerfile 文件名
      args:                  # 构建参数
        - ENV=production

如果同时指定了 buildimage,Compose 会用 image 的值作为构建后的镜像名称(相当于自动执行 docker build -t <image> .):

bash 复制代码
services:
  flask-app:
    build: .
    image: flask-redis-counter:latest

2.2 容器名称与重启策略

bash 复制代码
services:
  flask-app:
    container_name: flask-app     # 固定容器名(不指定则自动生成)
    restart: unless-stopped       # 重启策略

重启策略与 docker run --restart 一致,可选值:noalwayson-failureunless-stopped

注意 :如果计划使用 --scale 扩容,不要 指定固定的 container_name,因为每个副本必须有不同的名称。扩容场景下应让 Compose 自动生成名称。

2.3 端口映射

bash 复制代码
services:
  flask-app:
    ports:
      - "5000:5000"                # 宿主机端口:容器端口
      - "5001:5000"                # 可以映射多个端口
      - "127.0.0.1:5002:5000"      # 只绑定到宿主机特定 IP

等价于 docker run -p 5000:5000

2.4 环境变量

bash 复制代码
services:
  flask-app:
    environment:
      - FLASK_ENV=production
      - REDIS_HOST=redis
      - DB_PASSWORD=secret123      # ⚠️ 不要在 YAML 中硬编码敏感信息
    # 或从文件加载
    env_file:
      - .env

直接写在 YAML 中的环境变量会被提交到 Git 仓库,敏感信息(密码、API 密钥)不应硬编码。更好的方式是使用 .env 文件或 Docker Secrets(Swarm 模式),这将在第 13 篇和第 33 篇中专门展开。

2.5 数据卷挂载

bash 复制代码
services:
  flask-app:
    volumes:
      # 命名卷挂载
      - flask-logs:/app/logs
      # Bind Mount(宿主机绝对路径)
      - /home/user/config:/app/config:ro
      # 相对路径 Bind Mount(相对于 Compose 文件所在目录)
      - ./data:/app/data
      # 匿名卷
      - /app/tmp
  • 第一行对应 docker run -v flask-logs:/app/logs(命名卷,生产环境推荐)

  • 第二行对应 docker run -v /home/user/config:/app/config:ro(Bind Mount,开发环境常用)

  • 第三行是相对路径 Bind Mount,./data 相对于 docker-compose.yml 所在目录

  • 第四行只指定容器内路径,Docker 自动创建匿名卷

挂载目录的注意事项 :当使用 Bind Mount 时,宿主机目录会完全覆盖容器内对应路径。如果宿主机目录为空,容器内原来镜像中的文件会被"隐藏"。这常导致"挂载后服务启动失败"的困惑------比如挂载了一个空的 ./config 到容器的 /etc/nginx/conf.d,Nginx 就找不到任何配置文件了。

2.6 网络配置

bash 复制代码
services:
  flask-app:
    networks:
      - app-net
      - monitor-net          # 可以连接多个网络
    # 也可以为每个网络指定别名
    networks:
      app-net:
        aliases:
          - web
          - api

网络别名(aliases)让其他容器可以通过多个名称访问这个服务,等价于 docker run --network-alias

2.7 启动命令与入口点

bash 复制代码
services:
  redis:
    command: redis-server --appendonly yes --maxmemory 256mb
    # 或者用数组格式(推荐)
    command: ["redis-server", "--appendonly", "yes"]

command 会覆盖镜像默认的 CMD,但不覆盖 ENTRYPOINT

2.8 健康检查

bash 复制代码
services:
  redis:
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s

等价于 Dockerfile 中的 HEALTHCHECK 指令,但 Compose 中定义可以覆盖镜像内置的检查规则。

2.9 服务依赖与启动顺序

bash 复制代码
services:
  flask-app:
    depends_on:
      redis:
        condition: service_healthy   # 等待 Redis 健康检查通过
      db:
        condition: service_started   # 仅等待 db 容器启动(不等服务就绪)

三种条件:

  • service_started:仅等待容器启动

  • service_healthy:等待健康检查通过(推荐)

  • service_completed_successfully:等待一次性任务完成(如数据库迁移),退出码为 0

depends_on只控制启动顺序,不保证依赖服务完全就绪 。这就是为什么 condition: service_healthy 如此重要------它把"启动顺序"和"服务就绪"绑定在了一起。

2.10 资源限制(Compose v2+)

bash 复制代码
services:
  flask-app:
    deploy:
      resources:
        limits:
          cpus: '0.50'       # 最多使用 50% 单核 CPU
          memory: 256M        # 内存上限
        reservations:
          cpus: '0.25'        # 至少保留 25% 单核 CPU
          memory: 128M        # 至少保留内存

注意deploy.resourcesdocker compose up 中会被忽略 ,仅在 Swarm 模式(docker stack deploy)下生效。单机 Compose 场景下如需限制资源,请使用 docker run--cpus--memory 参数,或者在 Compose 文件中使用已弃用但依然有效的 V2 格式的 mem_limitcpus(不推荐,兼容性差)。在实际生产环境中,资源限制应交给 Kubernetes 的 ResourceQuota 和 LimitRange 管理。

2.11 完整示例:Flask + Redis 的 services 配置

综合以上知识,我们把 Flask + Redis 应用的 services 部分打磨得更完善:

bash 复制代码
services:
  redis:
    image: redis:alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    networks:
      - app-net
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s

  flask-app:
    image: flask-redis-counter:2.0
    restart: unless-stopped
    ports:
      - "5000:5000"
    environment:
      - FLASK_ENV=production
    volumes:
      - flask-logs:/app/logs
    networks:
      - app-net
    depends_on:
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s

三、networks 详解:自定义网络配置

Compose 默认会为每个项目创建一个默认网络(项目名_default),所有未显式声明 networks 的服务都会连接到这个默认网络。但我们强烈建议显式声明自定义网络,以获得更好的控制和隔离性。

bash 复制代码
networks:
  app-net:
    driver: bridge            # 默认驱动,单机桥接网络
  frontend:
    driver: bridge
  backend:
    driver: bridge

3.1 自定义子网和网关

bash 复制代码
networks:
  app-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.25.0.0/16
          gateway: 172.25.0.1

这在你需要固定 IP 段、与外部系统对接时非常有用。

3.2 外部网络

有时候你希望容器连接到一个已经存在的 Docker 网络(由其他项目或手动创建),而非由 Compose 自动创建:

bash 复制代码
networks:
  existing-net:
    external: true      # 表示该网络已在外部创建

然后在服务中引用:

bash 复制代码
services:
  flask-app:
    networks:
      - existing-net

external: true 告诉 Compose:"不要创建新网络,去连接那个已经存在的网络"。如果网络不存在,docker compose up 会报错。

3.3 网络别名

在同一网络中,可以通过 aliases 为服务定义额外的 DNS 名称:

bash 复制代码
services:
  redis:
    networks:
      app-net:
        aliases:
          - cache
          - redis-cache

现在,其他容器可以通过 cacheredis-cache 来解析到这个 Redis 容器,实现了简单的"服务发现别名"。在 K8s 中,这对应着 Service 的 namespec.clusterIP 的 DNS A 记录。

四、volumes 详解:持久化存储配置

bash 复制代码
volumes:
  redis-data:       # 命名卷,Docker 自动管理存储路径
  flask-logs:

4.1 卷驱动

默认使用 local 驱动(存储在宿主机本地磁盘)。也可以使用第三方驱动,如 NFS、AWS EBS:

bash 复制代码
volumes:
  nfs-data:
    driver: local
    driver_opts:
      type: nfs
      o: addr=192.168.1.100,nolock,soft,rw
      device: ":/export/data"

4.2 外部卷

和外部网络类似,可以使用已有的卷:

bash 复制代码
volumes:
  existing-data:
    external: true

这对于数据迁移或跨项目共享数据卷很有用。

五、完整的 docker-compose.yml 最终版

综合所有知识,我们为 Flask + Redis 计数器应用编写一份生产就绪的 Compose 文件:

bash 复制代码
# ============================================================
# Flask + Redis 计数器应用 ------ Docker Compose 生产配置
# 系列贯穿案例 v3.0
# ============================================================

services:
  redis:
    image: redis:alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    networks:
      app-net:
        aliases:
          - cache
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
      start_period: 5s

  flask-app:
    image: flask-redis-counter:2.0
    restart: unless-stopped
    ports:
      - "5000:5000"
    environment:
      - FLASK_ENV=production
      - REDIS_HOST=cache          # 使用网络别名
    volumes:
      - flask-logs:/app/logs
    networks:
      - app-net
    depends_on:
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 5s

volumes:
  redis-data:
  flask-logs:

networks:
  app-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.25.0.0/16

5.1 启动与验证

bash 复制代码
# 启动
docker compose up -d

# 查看资源
docker compose ps
docker network ls | grep app-net
docker volume ls | grep flask-redis-counter

# 测试别名
docker compose exec flask-app nslookup cache

六、命令速查表

七、本篇总结

  • services :每个服务涵盖镜像、端口、环境变量、卷挂载、网络、健康检查、依赖等全套配置,任何一个 docker run 参数在 Compose 中都有对应的声明式写法。

  • networks :自定义网络提供了 DNS 解析和网络隔离,aliases 实现服务别名,external 接入已存在的网络。

  • volumes:命名卷是生产持久化的首选,外部卷和第三方驱动扩展了存储能力。

  • 演进视角 :你现在看到的 servicesnetworksvolumes,在 Kubernetes 中会对应到 DeploymentServicePersistentVolumeClaim。Compose 是单机上的声明式编排,而 K8s 是集群级的声明式编排------核心理念完全一致,只是规模和抽象层次不同。

下一篇------第 13 篇:Compose 环境变量与配置管理,我们将深入探讨如何优雅地管理多环境配置,告别硬编码,让同一份 Compose 文件在开发、测试、生产环境中灵活切换。


想了解更多还可以去各个平台搜索「IT策士」,一起升级 IT 思维 !

相关推荐
CaffeinePro35 分钟前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax1 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH1 小时前
Koa和Express的区别
后端
MariaH1 小时前
Koa框架的使用
后端
luckdewei2 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某4 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy4 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom4 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github
用户1474853079748 小时前
CodeX使用Skill生成游戏美术和音乐资源,一分钟入门
后端
Melody1238 小时前
用 abort 中断 AI 流式请求,我之前做错了
后端