Docker Compose实战指南

本文基于 Docker Compose V2,所有内容均来自 Docker 官方文档和生产环境实践。

全文约 14800 字,建议收藏后阅读。读完本文,你将从 Docker Compose 新手成长为能够独立部署复杂多容器应用的专家。

一、Docker Compose 核心概念与底层原理

1.1 什么是 Docker Compose

Docker Compose 是 Docker 官方推出的多容器应用编排工具,它允许你使用一个 YAML 文件来定义和运行多个 Docker 容器组成的应用程序。

简单来说,如果你需要同时运行一个 Web 应用、一个数据库、一个缓存和一个消息队列,传统方式需要手动执行多个 docker run 命令,并且还要处理它们之间的网络连接、依赖关系等问题。而 Docker Compose 让你可以在一个文件中定义所有这些服务,然后用一条命令启动整个应用栈。

Docker Compose 的核心价值:

  • 一键部署:一条命令启动/停止整个应用
  • 环境一致性:开发、测试、生产环境使用相同的配置
  • 依赖管理:自动处理服务之间的启动顺序和依赖关系
  • 可重复性:相同的配置在任何地方都能得到相同的结果
  • 声明式配置:描述"想要什么",而不是"怎么做"

1.2 Docker Compose V1 vs V2:为什么必须升级到 V2

重要提醒:Compose V1(docker-compose 命令)已于 2023 年 7 月停止接收更新,并且不再包含在新的 Docker Desktop 版本中。所有新的开发和部署都应该使用 Compose V2(docker compose 命令)

特性 Compose V1 Compose V2
命令格式 docker-compose(带连字符) docker compose(空格分隔)
实现语言 Python Go
集成方式 独立二进制文件 Docker CLI 插件
性能 较慢 显著提升(特别是构建速度)
容器命名 使用下划线分隔(project_service_1 使用连字符分隔(project-service-1
支持状态 已弃用 官方推荐,持续更新
BuildKit 支持 有限 原生支持,默认启用

升级方法:

  • Windows/macOS:升级到最新版 Docker Desktop 即可自动获得 Compose V2
  • Linux:通过 Docker 官方仓库安装或手动下载二进制文件到 $HOME/.docker/cli-plugins/ 目录

1.3 Docker Compose 工作原理

Docker Compose 本身不创建容器 ,它只是一个客户端工具,通过 Docker API 与 Docker Engine 通信,将 Compose 文件中的声明转换为一系列 Docker API 调用。

关键底层细节:

  1. 项目(Project) :Compose 将一个应用视为一个项目,默认使用当前目录的名称作为项目名称。你可以通过 --project-name 标志或 COMPOSE_PROJECT_NAME 环境变量覆盖。
  2. 服务(Service) :项目由多个服务组成,每个服务对应一个或多个相同配置的容器。
  3. 网络隔离:Compose 会为每个项目创建一个独立的桥接网络,同一项目中的所有服务默认都连接到这个网络,并且可以通过服务名称互相访问。
  4. 数据卷管理:Compose 会自动管理命名数据卷,确保数据在容器重启或重建时不会丢失。

1.4 Docker Compose 的适用场景与局限性

适用场景:

  • 开发环境:快速搭建一致的开发环境
  • 测试环境:自动化测试的环境准备
  • 小型到中型生产环境:单节点部署
  • CI/CD 流水线:构建和测试应用
  • 演示和原型:快速展示应用功能

局限性:

  • 仅支持单节点部署,不支持多节点集群
  • 没有内置的服务发现和负载均衡(需要配合反向代理)
  • 没有滚动更新和蓝绿部署功能(需要手动实现)
  • 不适合超大规模应用部署

注意:对于大规模生产环境,应该使用 Kubernetes 而不是 Docker Compose。但对于 90% 的中小型应用,Docker Compose 已经足够强大且简单易用。

二、docker-compose.yml 文件详解(Compose Specification)

2.1 文件格式与基本结构

Compose 文件使用 YAML 格式编写,默认文件名是 compose.yamldocker-compose.yml(两者都支持,推荐使用 compose.yaml)。

重要更新:自 Docker Compose v1.27 起,version 字段已被弃用 。现在所有新的 Compose 文件都应该省略 version 字段,直接使用最新的 Compose Specification。

基本结构:

yaml 复制代码
# 顶级元素:services(必需)
services:
  # 服务1定义
  service-name-1:
    # 服务配置
    image: nginx:alpine
    ports:
      - "80:80"
  
  # 服务2定义
  service-name-2:
    # 服务配置
    build: .
    depends_on:
      - service-name-1

# 顶级元素:networks(可选)
networks:
  # 自定义网络定义
  my-network:
    driver: bridge

# 顶级元素:volumes(可选)
volumes:
  # 自定义数据卷定义
  my-volume:

# 顶级元素:configs(可选)
configs:
  # 配置文件定义
  my-config:
    file: ./config.conf

# 顶级元素:secrets(可选)
secrets:
  # 敏感信息定义
  my-secret:
    file: ./secret.txt

2.2 services 顶级元素详解

services 是 Compose 文件中唯一必需的顶级元素,用于定义应用中的所有服务。每个服务对应一个或多个容器。

2.2.1 基本配置

image:指定服务使用的镜像。

yaml 复制代码
services:
  nginx:
    image: nginx:1.25-alpine  # 推荐使用具体版本,不要使用 latest

container_name:指定容器名称。

yaml 复制代码
services:
  nginx:
    image: nginx:alpine
    container_name: my-nginx  # 自定义容器名称

注意 :如果指定了 container_name,则该服务不能进行扩容(scale),因为容器名称必须是唯一的。

command:覆盖容器默认的 CMD 指令。

bash 复制代码
services:
  app:
    image: node:20-alpine
    command: npm start  # 字符串形式
    
    # 或者数组形式(推荐,避免 shell 解析问题)
    command: ["npm", "start"]

entrypoint:覆盖容器默认的 ENTRYPOINT 指令。

arduino 复制代码
services:
  app:
    image: node:20-alpine
    entrypoint: ["/app/entrypoint.sh"]

working_dir:设置容器内的工作目录。

yaml 复制代码
services:
  app:
    image: node:20-alpine
    working_dir: /app

user:指定运行容器内进程的用户。

makefile 复制代码
services:
  app:
    image: node:20-alpine
    user: "1000:1000"  # UID:GID

2.2.2 构建配置

build:指定从 Dockerfile 构建镜像。

yaml 复制代码
# 简单形式:指定上下文路径
services:
  app:
    build: .  # 使用当前目录下的 Dockerfile 构建

# 完整形式
services:
  app:
    build:
      context: ./app  # 构建上下文路径
      dockerfile: Dockerfile.prod  # 指定 Dockerfile 名称
      args:  # 构建参数
        NODE_ENV: production
        VERSION: 1.0.0
      target: production  # 多阶段构建的目标阶段
      cache_from:  # 构建缓存源
        - myapp:latest
      network: host  # 构建时使用的网络

重要提示: 构建上下文是 Docker 守护进程可以访问的文件和目录的集合。Docker 会将上下文中的所有文件发送给守护进程,因此应该尽量减小上下文大小,将不需要的文件添加到 .dockerignore 中。

2.2.3 端口配置

ports:将容器端口映射到主机端口。

makefile 复制代码
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"  # 主机端口:容器端口
      - "443:443"
      - "127.0.0.1:8080:80"  # 只绑定到 localhost
      - "8000-8010:8000-8010"  # 端口范围映射
      - "9000"  # 随机映射主机端口到容器 9000 端口

expose:暴露端口给同一网络中的其他服务,但不映射到主机。

yaml 复制代码
services:
  db:
    image: postgres:16-alpine
    expose:
      - "5432"  # 同一网络中的服务可以通过 db:5432 访问

2.2.4 环境变量配置

environment:设置环境变量。

ini 复制代码
services:
  db:
    image: postgres:16-alpine
    environment:
      # 数组形式
      - POSTGRES_DB=myapp
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=secret
      
      # 或者对象形式(推荐)
      POSTGRES_DB: myapp
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: secret

env_file:从文件中加载环境变量。

yaml 复制代码
services:
  app:
    image: node:20-alpine
    env_file:
      - .env  # 默认加载
      - .env.production  # 额外的环境文件

最佳实践 :永远不要在 Compose 文件中硬编码敏感信息(如密码、API 密钥等)。应该使用 .env 文件,并将 .env 添加到 .gitignore 中。对于高度敏感的数据,应该使用 Docker Secrets。

2.2.5 数据卷配置

volumes:挂载数据卷或主机目录到容器。

bash 复制代码
services:
  db:
    image: postgres:16-alpine
    volumes:
      # 命名数据卷(推荐,由 Docker 管理)
      - postgres_data:/var/lib/postgresql/data
      
      # 绑定挂载(主机目录)
      - ./config:/etc/postgresql/config:ro  # 只读挂载
      - ./logs:/var/log/postgresql:rw  # 读写挂载(默认)
      
      # tmpfs 挂载(临时文件系统,存储在内存中)
      - type: tmpfs
        target: /tmp
        tmpfs:
          size: 100M

# 必须在顶级 volumes 中声明命名数据卷
volumes:
  postgres_data:

三种挂载类型对比:

类型 语法 特点 适用场景
命名数据卷 volume_name:/container/path Docker 管理,性能好,数据持久化 数据库数据、应用状态
绑定挂载 /host/path:/container/path 主机目录直接挂载,开发时方便 开发环境代码挂载、配置文件
tmpfs type: tmpfs 内存中存储,速度快,数据不持久 临时文件、缓存

2.2.6 网络配置

networks:指定服务连接的网络。

yaml 复制代码
services:
  web:
    image: nginx:alpine
    networks:
      - frontend
      - backend
  
  app:
    image: node:20-alpine
    networks:
      - backend
  
  db:
    image: postgres:16-alpine
    networks:
      - backend

networks:
  frontend:
  backend:

重要 :默认情况下,Compose 会创建一个名为 {project_name}_default 的网络,所有服务都会连接到这个网络。只有当你需要网络隔离时,才需要自定义网络。

network_mode:指定网络模式。

yaml 复制代码
services:
  app:
    image: node:20-alpine
    network_mode: bridge  # 默认
    # network_mode: host  # 使用主机网络
    # network_mode: none  # 禁用网络
    # network_mode: service:web  # 共享另一个服务的网络命名空间

2.2.7 依赖关系与启动顺序

depends_on:指定服务之间的依赖关系,决定启动顺序。

yaml 复制代码
services:
  web:
    image: nginx:alpine
    depends_on:
      - app
      - db
  
  app:
    image: node:20-alpine
    depends_on:
      - db
      - redis
  
  db:
    image: postgres:16-alpine
  
  redis:
    image: redis:7-alpine

非常重要的注意事项:

depends_on 只保证容器的启动顺序,不保证服务的就绪顺序。也就是说,它只会等待容器启动,而不会等待容器内的应用程序完全启动并准备好接受请求。

例如,上面的配置中,app 服务会在 dbredis 容器启动后立即启动,但此时 PostgreSQL 可能还在初始化过程中,无法接受数据库连接,导致 app 服务启动失败。

解决方案:

  1. 应用层重试:在应用程序中实现数据库连接重试逻辑
  2. 健康检查 :使用 healthcheck 配合 depends_on 的条件语法
  3. 等待脚本 :使用 wait-for-it.sh 等工具等待依赖服务就绪

健康检查配合 depends_on(推荐):

bash 复制代码
services:
  app:
    image: node:20-alpine
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
  
  db:
    image: postgres:16-alpine
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
  
  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

2.2.8 重启策略

restart:指定容器的重启策略。

yaml 复制代码
services:
  app:
    image: node:20-alpine
    restart: no  # 默认,不重启
    # restart: always  # 总是重启
    # restart: on-failure  # 仅在失败时重启
    # restart: unless-stopped  # 除非手动停止,否则总是重启

生产环境推荐使用 restart: unless-stopped,这样容器在崩溃或 Docker 重启时会自动重启,但如果你手动停止了容器,它不会自动重启。

2.2.9 健康检查

healthcheck:配置容器的健康检查,用于判断容器内的应用是否正常运行。

bash 复制代码
services:
  web:
    image: nginx:alpine
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s  # 检查间隔
      timeout: 10s  # 超时时间
      retries: 3  # 重试次数
      start_period: 30s  # 启动后等待多久开始检查
      disable: false  # 是否禁用健康检查

test 命令的三种形式:

  • ["CMD", "command", "arg1", "arg2"]:直接执行命令,不通过 shell
  • ["CMD-SHELL", "command arg1 arg2"]:通过 shell 执行命令
  • "command arg1 arg2":等同于 ["CMD-SHELL", "command arg1 arg2"]

2.2.10 资源限制

deploy.resources:配置容器的资源限制和预留。

yaml 复制代码
services:
  app:
    image: node:20-alpine
    deploy:
      resources:
        limits:  # 资源上限
          cpus: "1.0"  # 最多使用 1 个 CPU 核心
          memory: 512M  # 最多使用 512MB 内存
        reservations:  # 资源预留
          cpus: "0.25"  # 预留 0.25 个 CPU 核心
          memory: 128M  # 预留 128MB 内存

生产环境必须配置资源限制,防止单个容器占用所有主机资源,导致其他服务无法运行。

2.3 networks 顶级元素详解

networks 用于定义自定义网络。默认情况下,Compose 会创建一个桥接网络,但你可以自定义网络的驱动、IP 地址范围等。

yaml 复制代码
networks:
  frontend:
    driver: bridge  # 默认驱动
    driver_opts:
      com.docker.network.bridge.enable_icc: "true"  # 启用容器间通信
  
  backend:
    driver: bridge
    ipam:  # IP 地址管理
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1
  
  external-network:
    external: true  # 使用已存在的外部网络
    name: my-existing-network  # 外部网络的实际名称

2.4 volumes 顶级元素详解

volumes 用于定义命名数据卷。

bash 复制代码
volumes:
  postgres_data:
    driver: local  # 默认驱动
    driver_opts:
      type: none
      o: bind
      device: /data/postgres  # 绑定到主机指定目录
  
  redis_data:
    external: true  # 使用已存在的外部数据卷
    name: my-existing-redis-volume

2.5 configs 与 secrets 顶级元素详解

configssecrets 用于管理配置文件和敏感信息,避免将它们硬编码在镜像或 Compose 文件中。

configs 示例:

yaml 复制代码
services:
  nginx:
    image: nginx:alpine
    configs:
      - source: nginx-config
        target: /etc/nginx/nginx.conf
        mode: 0444

configs:
  nginx-config:
    file: ./nginx.conf  # 从文件加载配置

secrets 示例:

yaml 复制代码
services:
  db:
    image: postgres:16-alpine
    secrets:
      - postgres-password
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/postgres-password

secrets:
  postgres-password:
    file: ./postgres-password.txt  # 从文件加载密码

注意 :在单机 Docker 环境中,secrets 只是简单地将文件挂载到容器的 /run/secrets/ 目录,并没有加密。但它仍然比硬编码密码要好,因为密码不会出现在 Compose 文件或镜像中。

2.6 变量替换与 .env 文件

Docker Compose 支持在 Compose 文件中使用环境变量进行变量替换。

基本语法:

ruby 复制代码
services:
  db:
    image: postgres:${POSTGRES_VERSION:-16}-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}

变量修饰符:

  • ${VAR:-default}:如果 VAR 未设置或为空,使用 default
  • ${VAR-default}:如果 VAR 未设置,使用 default
  • ${VAR:?error}:如果 VAR 未设置或为空,抛出错误并退出
  • ${VAR?error}:如果 VAR 未设置,抛出错误并退出

.env 文件: Compose 会自动从当前目录下的 .env 文件加载环境变量。

ini 复制代码
# .env 文件示例
POSTGRES_VERSION=16
POSTGRES_DB=myapp
POSTGRES_USER=postgres
POSTGRES_PASSWORD=supersecretpassword

最佳实践:

  1. 提供一个 .env.example 文件作为模板,包含所有必需的环境变量
  2. .env 添加到 .gitignore 中,不要提交到版本控制
  3. 使用不同的 .env 文件对应不同的环境(.env.dev, .env.prod
  4. 使用 ${VAR:?error} 语法验证必需的环境变量,实现快速失败

三、Docker Compose 常用命令详解

3.1 基本生命周期命令

docker compose up

创建并启动所有服务。

bash 复制代码
# 前台运行,显示所有日志
docker compose up

# 后台运行(守护进程模式)
docker compose up -d

# 启动前重新构建镜像
docker compose up --build

# 强制重新创建所有容器
docker compose up --force-recreate

# 只启动指定的服务
docker compose up -d web db

docker compose down

停止并删除所有服务容器、网络。

csharp 复制代码
# 停止并删除容器和网络
docker compose down

# 同时删除数据卷(谨慎使用!会丢失所有数据)
docker compose down -v

# 同时删除镜像
docker compose down --rmi all

# 删除孤立的容器(不在 Compose 文件中定义的容器)
docker compose down --remove-orphans

docker compose start/stop/restart

启动、停止或重启已存在的服务。

bash 复制代码
# 启动所有服务
docker compose start

# 启动指定服务
docker compose start web

# 停止所有服务
docker compose stop

# 停止指定服务
docker compose stop db

# 重启所有服务
docker compose restart

# 重启指定服务
docker compose restart app

3.2 查看与调试命令

docker compose ps

列出所有服务及其状态。

bash 复制代码
# 列出运行中的服务
docker compose ps

# 列出所有服务(包括已停止的)
docker compose ps -a

docker compose logs

查看服务日志。

ini 复制代码
# 查看所有服务的日志
docker compose logs

# 查看指定服务的日志
docker compose logs web

# 实时跟踪日志
docker compose logs -f

# 显示最后 N 行日志
docker compose logs --tail=100

# 显示日志时间戳
docker compose logs -t

docker compose exec

在运行中的容器内执行命令。

bash 复制代码
# 在 app 容器中启动 bash
docker compose exec app bash

# 以 root 用户执行命令
docker compose exec -u root app bash

# 执行一次性命令
docker compose exec db psql -U postgres myapp

docker compose run

运行一个一次性的服务容器。

bash 复制代码
# 运行 app 服务并执行 npm test
docker compose run app npm test

# 运行时不启动依赖服务
docker compose run --no-deps app npm test

# 运行后自动删除容器
docker compose run --rm app npm test

docker compose exec vs docker compose run

  • exec:在已经运行的容器中执行命令
  • run:创建一个新的容器并执行命令,执行完后容器会停止

3.3 构建与镜像命令

docker compose build

构建服务镜像。

perl 复制代码
# 构建所有服务的镜像
docker compose build

# 构建指定服务的镜像
docker compose build app

# 不使用缓存构建
docker compose build --no-cache

# 构建并拉取最新的基础镜像
docker compose build --pull

docker compose pull

拉取服务使用的镜像。

bash 复制代码
# 拉取所有服务的镜像
docker compose pull

# 拉取指定服务的镜像
docker compose pull db

docker compose push

推送服务镜像到镜像仓库。

perl 复制代码
# 推送所有服务的镜像
docker compose push

# 推送指定服务的镜像
docker compose push app

3.4 其他常用命令

docker compose config

验证并查看合并后的 Compose 配置。

lua 复制代码
# 验证配置是否正确
docker compose config

# 查看合并后的完整配置
docker compose config --no-interpolate

docker compose top

显示服务容器中运行的进程。

bash 复制代码
# 显示所有服务的进程
docker compose top

# 显示指定服务的进程
docker compose top app

docker compose stats

显示服务容器的资源使用情况。

bash 复制代码
# 显示所有服务的资源使用情况
docker compose stats

# 显示指定服务的资源使用情况
docker compose stats app db

docker compose scale

扩容服务。

ini 复制代码
# 将 app 服务扩容到 3 个实例
docker compose up --scale app=3 -d

# 或者
docker compose scale app=3

注意 :如果服务指定了 container_nameports 映射到固定主机端口,则不能进行扩容。

docker compose watch

自动监控文件变化并重新构建和重启服务(Compose V2.22+)。

bash 复制代码
# 启动文件监控
docker compose watch

这是开发环境的一个非常有用的功能,可以实现代码热重载。

四、实战案例:从简单到复杂的应用部署

4.1 案例一:最简单的 Nginx 静态网站

文件结构:

css 复制代码
.
├── docker-compose.yml
└── html
    └── index.html

docker-compose.yml:

yaml 复制代码
services:
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
    restart: unless-stopped

html/index.html:

xml 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>Hello Docker Compose</title>
</head>
<body>
    <h1>Hello Docker Compose!</h1>
</body>
</html>

启动命令:

复制代码
docker compose up -d

访问 http://localhost 即可看到页面。

4.2 案例二:Node.js + PostgreSQL + Redis 应用

文件结构:

bash 复制代码
.
├── .env
├── docker-compose.yml
└── app
    ├── Dockerfile
    ├── package.json
    └── server.js

.env:

ini 复制代码
POSTGRES_DB=myapp
POSTGRES_USER=postgres
POSTGRES_PASSWORD=supersecretpassword
REDIS_URL=redis://redis:6379
DATABASE_URL=postgres://postgres:supersecretpassword@db:5432/myapp

docker-compose.yml:

bash 复制代码
services:
  app:
    build: ./app
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

app/Dockerfile:

sql 复制代码
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

EXPOSE 3000

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

启动命令:

复制代码
docker compose up -d

4.3 案例三:带 Nginx 反向代理和 HTTPS 的完整应用

文件结构:

erlang 复制代码
.
├── .env
├── docker-compose.yml
├── nginx
│   ├── Dockerfile
│   └── nginx.conf
├── certs
│   ├── fullchain.pem
│   └── privkey.pem
└── app
    ├── Dockerfile
    └── ...

docker-compose.yml:

yaml 复制代码
services:
  nginx:
    build: ./nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app
    restart: unless-stopped
    networks:
      - frontend
      - backend

  app:
    build: ./app
    environment:
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - backend
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - backend

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  postgres_data:
  redis_data:

nginx/nginx.conf:

ini 复制代码
events {}

http {
    server {
        listen 80;
        server_name example.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl;
        server_name example.com;

        ssl_certificate /etc/nginx/certs/fullchain.pem;
        ssl_certificate_key /etc/nginx/certs/privkey.pem;

        location / {
            proxy_pass http://app:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

4.4 案例四:多环境配置(开发/生产)

使用多个 Compose 文件实现不同环境的配置。

基础配置:docker-compose.yml

yaml 复制代码
services:
  app:
    build: ./app
    environment:
      DATABASE_URL: ${DATABASE_URL}
      REDIS_URL: ${REDIS_URL}

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  postgres_data:
  redis_data:

开发环境覆盖:docker-compose.override.yml(默认自动加载)

yaml 复制代码
services:
  app:
    ports:
      - "3000:3000"
    volumes:
      - ./app:/app
      - /app/node_modules
    environment:
      NODE_ENV: development
    command: npm run dev

  db:
    ports:
      - "5432:5432"

  redis:
    ports:
      - "6379:6379"

生产环境覆盖:docker-compose.prod.yml

bash 复制代码
services:
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - app
    restart: unless-stopped

  app:
    environment:
      NODE_ENV: production
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

  db:
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

启动开发环境:

复制代码
docker compose up -d

启动生产环境:

复制代码
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

五、高级特性与最佳实践

5.1 使用 Profiles 管理可选服务

Profiles 允许你定义哪些服务在特定环境下应该启动。

yaml 复制代码
services:
  app:
    image: node:20-alpine
    # 总是启动

  db:
    image: postgres:16-alpine
    # 总是启动

  redis:
    image: redis:7-alpine
    profiles: ["with-redis"]  # 只有指定 with-redis profile 时才启动

  adminer:
    image: adminer:latest
    ports:
      - "8080:8080"
    profiles: ["debug"]  # 只有指定 debug profile 时才启动

启动命令:

csharp 复制代码
# 只启动 app 和 db
docker compose up -d

# 启动 app、db 和 redis
docker compose --profile with-redis up -d

# 启动所有服务
docker compose --profile "*" up -d

5.2 使用 extends 共享配置

extends 允许你从另一个 Compose 文件或同一文件中的另一个服务继承配置。

yaml 复制代码
# base.yml
services:
  base-app:
    build: ./app
    environment:
      NODE_ENV: production
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
yaml 复制代码
# docker-compose.yml
services:
  app1:
    extends:
      file: base.yml
      service: base-app
    environment:
      APP_NAME: app1

  app2:
    extends:
      file: base.yml
      service: base-app
    environment:
      APP_NAME: app2

5.3 日志配置最佳实践

配置日志驱动和日志轮转:

python 复制代码
services:
  app:
    image: node:20-alpine
    logging:
      driver: json-file  # 默认驱动
      options:
        max-size: "10m"  # 单个日志文件最大大小
        max-file: "3"    # 最多保留 3 个日志文件

生产环境推荐配置:

arduino 复制代码
x-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

services:
  app:
    image: node:20-alpine
    logging: *default-logging

  db:
    image: postgres:16-alpine
    logging: *default-logging

  redis:
    image: redis:7-alpine
    logging: *default-logging

5.4 安全最佳实践

  1. 不要以 root 用户运行容器

    makefile 复制代码
    services:
      app:
        image: node:20-alpine
        user: "1000:1000"  # 使用非 root 用户
  2. 使用只读文件系统

    yaml 复制代码
    services:
      app:
        image: node:20-alpine
        read_only: true
        tmpfs:
          - /tmp
          - /app/tmp
  3. 删除不必要的 capabilities

    yaml 复制代码
    services:
      app:
        image: node:20-alpine
        cap_drop:
          - ALL
  4. 不要暴露不必要的端口

  5. 使用具体的镜像标签,不要使用 latest

  6. 定期更新基础镜像

  7. 扫描镜像漏洞

5.5 性能优化最佳实践

  1. 使用多阶段构建减小镜像大小
  2. 合理配置资源限制
  3. 使用命名数据卷而不是绑定挂载
  4. 避免在容器中运行多个进程
  5. 使用缓存加速构建
  6. 使用 .dockerignore 减小构建上下文

六、生产环境部署与运维

6.1 生产环境部署清单

在将应用部署到生产环境之前,请确保你已经完成了以下检查:

  • 所有服务都配置了 restart: unless-stopped
  • 所有关键服务都配置了健康检查
  • 所有服务都配置了资源限制
  • 所有持久化数据都使用了命名数据卷
  • 没有硬编码任何敏感信息
  • 日志配置了轮转
  • 应用运行在非 root 用户下
  • 只暴露了必要的端口
  • 配置了 HTTPS
  • 有备份策略
  • 有监控和告警

6.2 备份与恢复

备份数据卷:

bash 复制代码
# 创建备份容器,将数据卷内容打包
docker run --rm \
  -v myapp_postgres_data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/postgres_backup_$(date +%Y%m%d).tar.gz -C /data .

恢复数据卷:

bash 复制代码
# 停止相关服务
docker compose stop db

# 创建恢复容器,将备份内容解压到数据卷
docker run --rm \
  -v myapp_postgres_data:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/postgres_backup_20260420.tar.gz -C /data

# 启动服务
docker compose start db

6.3 滚动更新

Docker Compose 本身不支持真正的滚动更新,但你可以通过以下方式实现类似的效果:

ini 复制代码
# 构建新镜像
docker compose build app

# 启动新的容器实例
docker compose up -d --scale app=2 --no-recreate

# 等待新容器健康检查通过
sleep 30

# 停止旧容器
docker compose stop app-old

# 删除旧容器
docker compose rm -f app-old

# 缩容回原来的数量
docker compose up -d --scale app=1 --no-recreate

对于更复杂的更新需求,建议使用 Kubernetes。

6.4 监控与告警

使用 docker stats 监控资源使用:

复制代码
docker compose stats

使用 Prometheus + Grafana 进行全面监控:

yaml 复制代码
services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    restart: unless-stopped

volumes:
  grafana_data:

七、常见问题排查与解决方案

7.1 端口冲突问题

错误信息:

vbnet 复制代码
Error response from daemon: driver failed programming external connectivity on endpoint ...: Bind for 0.0.0.0:80 failed: port is already allocated

解决方案:

  1. 查找占用端口的进程:

    css 复制代码
    sudo lsof -i :80
  2. 停止占用端口的进程

  3. 或者修改 Compose 文件中的端口映射

7.2 服务启动顺序问题

问题描述: 应用服务在数据库服务准备好之前就启动了,导致连接失败。

解决方案:

  1. 使用健康检查配合 depends_on 的条件语法(推荐)
  2. 在应用程序中实现重试逻辑
  3. 使用等待脚本

7.3 数据丢失问题

问题描述: 执行 docker compose down 后,数据库数据丢失。

原因: 没有使用命名数据卷,而是使用了匿名数据卷。

解决方案:

  1. 始终使用命名数据卷存储持久化数据
  2. 不要轻易使用 docker compose down -v,它会删除所有数据卷
  3. 定期备份数据

7.4 镜像拉取失败问题

解决方案:

  1. 检查网络连接
  2. 配置镜像加速器
  3. 登录到私有镜像仓库
  4. 检查镜像名称和标签是否正确

7.5 权限问题

错误信息:

arduino 复制代码
Permission denied: '/app/data'

解决方案:

  1. 确保容器内的用户对挂载的目录有读写权限
  2. 使用 user 指令指定与主机目录权限匹配的 UID 和 GID
  3. 修改主机目录的权限

八、总结

Docker Compose 是一个非常强大且简单易用的多容器应用编排工具。它通过声明式的配置文件,让你可以轻松地定义、运行和管理复杂的多容器应用。 希望本文能够帮助你全面掌握 Docker Compose,让你的开发和部署工作更加高效和轻松。如果你觉得本文对你有帮助,欢迎点赞、收藏和分享。

相关推荐
j_xxx404_3 小时前
万字长文爆肝:彻底弄懂Linux文件系统(Ext2),从Inode、Block到Dentry核心机制全解析
linux·运维·服务器
Zn_lunar3 小时前
autodl tizi+codex cli
运维·服务器·网络
鹅是开哥3 小时前
XXL-Job Docker 部署中“登录无响应”的排查与解决
运维·docker·容器
逻极3 小时前
MySQL 从入门到精通:一个老 DBA 的实战心法
运维·数据库·mysql·从入门到精通·mysql从入门到精通
cui_ruicheng4 小时前
Linux IO入门(三):手写一个简易的 mystdio 库
linux·运维·服务器
telllong4 小时前
MCP协议实战:30分钟给Claude接上你公司的内部API
linux·运维·服务器
正在走向自律4 小时前
KingbaseES 基础 SQL 语法与日常运维实操手册
运维·数据库·sql·kingbasees
实心儿儿4 小时前
Linux —— 进程概念 - 程序地址空间
linux·运维·算法
buhuizhiyuci4 小时前
linux篇-应用商店:“yum / apt“ 的详解
linux·运维·服务器