单独使用 Docker 时,我们通常需要执行:

docker run

来启动一个容器。

但一个实际项目往往并不只有一个容器。

例如一个 PHP/Hyperf 项目可能需要:

Nginx
PHP / Hyperf
MySQL
Redis
RabbitMQ

如果全部手动使用 docker run 管理,不仅命令很长,而且容器之间的网络、Volume、环境变量、启动顺序等都需要自己维护。

Docker Compose 就是为解决这个问题而设计的。

Docker 官方将 Compose 定义为用于定义和运行多容器应用的工具,可以在一个 Compose 文件中定义服务、网络和 Volume,并通过命令统一管理整个应用栈。当前推荐使用 Compose Specification。


1. Docker Compose 是什么

Docker Compose 的核心思想是:

一个应用
   │
   ├── PHP
   ├── Nginx
   ├── MySQL
   └── Redis
          │
          ▼
     compose.yaml
          │
          ▼
docker compose up -d

也就是说:

Docker 负责容器,Compose 负责组织容器。

例如:

services:

  php:
    image: php:8.3-cli

  mysql:
    image: mysql:8.4

  redis:
    image: redis:7

然后:

docker compose up -d

Compose 就可以根据配置创建并启动这些服务。


2. Compose 文件基本结构

当前推荐使用:

compose.yaml

也可以使用:

docker-compose.yaml
docker-compose.yml

一个最简单的 Compose 文件:

services:

  nginx:
    image: nginx:1.27

  redis:
    image: redis:7

核心结构:

services
   │
   ├── nginx
   │
   └── redis

其中:

nginx:

和:

redis:

是两个 Service。

Compose Specification 的核心顶层元素包括 services、networks、volumes、configs、secrets 等。


3. Service:Compose 的核心

Compose 最重要的概念就是:

services:

一个 Service 描述一个应用组件。

例如:

services:

  php:
    image: php:8.3-cli

  mysql:
    image: mysql:8.4

  redis:
    image: redis:7

可以理解成:

php service
    ↓
PHP container

mysql service
    ↓
MySQL container

redis service
    ↓
Redis container

Service 通常至少需要定义:

image:

或者:

build:

使用 image

services:

  redis:
    image: redis:7

表示直接使用已有镜像。

使用 build

services:

  php:
    build:
      context: .
      dockerfile: Dockerfile

表示根据项目中的 Dockerfile 构建镜像。

因此 Dockerfile 与 Compose 的关系是:

Dockerfile
    ↓
构建 Image

Compose
    ↓
组织和运行 Container

Compose 的 build 配置就是用来定义如何为 Service 构建镜像的。


4. Service 常用配置

一个实际 Service 通常会涉及:

services:

  app:
    build:
      context: .
      dockerfile: Dockerfile

    ports:
      - "8080:8080"

    environment:
      APP_ENV: production

    volumes:
      - ./runtime:/app/runtime

    depends_on:
      - mysql
      - redis

    networks:
      - app

这些配置分别解决不同问题。

image

image: nginx:1.27

指定镜像。

build

build:
  context: .

指定镜像构建方式。

ports

ports:
  - "8080:80"

将:

宿主机 8080
      ↓
容器 80

进行映射。

environment

environment:
  APP_ENV: production

设置容器运行环境。

volumes

volumes:
  - mysql-data:/var/lib/mysql

挂载持久化数据。

depends_on

depends_on:
  - mysql

定义服务依赖关系。

networks

networks:
  - app

把服务加入指定网络。


5. Network:Compose 中的服务通信

Compose 会为应用创建网络,使同一个 Compose 项目中的服务可以互相通信。

例如:

services:

  php:
    image: php:8.3-cli

  mysql:
    image: mysql:8.4

PHP 连接 MySQL 时,可以使用:

mysql

作为主机名。

例如:

DB_HOST=mysql
DB_PORT=3306

而不是:

DB_HOST=localhost

因为:

localhost

代表当前容器。

而:

mysql

代表 Compose 中名为 mysql 的 Service。

可以理解为:

                Docker Network

        ┌───────────────────────┐
        │                       │
        │   PHP                 │
        │    │                  │
        │    │ mysql:3306       │
        │    ▼                  │
        │   MySQL               │
        │                       │
        │   Redis               │
        │                       │
        └───────────────────────┘

如果需要显式定义网络:

networks:
  app:

然后:

services:

  php:
    networks:
      - app

  mysql:
    networks:
      - app

这样就可以明确控制服务属于哪个网络。


6. Volume:管理持久化数据

容器本身适合运行应用,但数据库数据不能依赖容器生命周期。

例如:

services:

  mysql:
    image: mysql:8.4
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:

这里:

mysql-data

是一个命名 Volume。

关系:

mysql-data
     │
     ▼
/var/lib/mysql
     │
     ▼
MySQL 数据

Compose 的顶层 volumes 用于定义可持久化、可复用的数据存储。

Bind Mount

开发环境也经常使用:

volumes:
  - ./:/app

表示:

宿主机项目目录
      ↓
容器 /app

这样修改代码后,容器可以直接看到代码变化。

因此:

Named Volume

更适合:

数据库
Redis 数据
持久化业务数据

而:

Bind Mount

非常适合:

本地开发
代码同步
配置文件

7. 环境变量与配置管理

Compose 支持直接定义:

environment:
  APP_ENV: production
  DB_HOST: mysql
  DB_PORT: 3306

也可以:

environment:
  APP_ENV: ${APP_ENV}
  DB_PASSWORD: ${DB_PASSWORD}

然后通过环境变量提供:

APP_ENV=production
DB_PASSWORD=******

实际项目中,更推荐把不同环境的配置与 Compose 文件分离。

例如:

compose.yaml
.env
.env.example

.env.example:

APP_ENV=local
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=app
DB_USERNAME=root
DB_PASSWORD=

而真正的:

.env

不应该提交敏感生产配置到 Git。

Compose 还支持 configs 和 secrets 等机制,用于将配置或敏感数据显式授予指定 Service。


8. Service 依赖与启动顺序

例如 PHP 依赖 MySQL:

services:

  php:
    build:
      context: .
    depends_on:
      - mysql

  mysql:
    image: mysql:8.4

表示 PHP Service 依赖 MySQL。

但需要注意:

depends_on 不等于“数据库已经可以正常接受连接”。

它主要描述 Compose 中的服务依赖关系。

例如:

MySQL Container 启动
        ↓
MySQL 开始初始化
        ↓
MySQL 还不能连接
        ↓
PHP 启动
        ↓
PHP 立即连接 MySQL
        ↓
连接失败

因此实际生产项目还需要考虑:

  • Healthcheck

  • 应用重试

  • 数据库初始化状态

  • 服务启动策略

例如:

services:

  mysql:
    image: mysql:8.4

    healthcheck:
      test:
        - CMD
        - mysqladmin
        - ping
        - "-h"
        - localhost
      interval: 5s
      timeout: 3s
      retries: 10

  php:
    build: .
    depends_on:
      mysql:
        condition: service_healthy

这样可以让应用依赖数据库的健康状态,而不仅仅是容器是否已经启动。


9. Compose 常用命令

Compose 最重要的命令是:

docker compose up

后台启动:

docker compose up -d

如果需要重新构建:

docker compose up -d --build

查看服务:

docker compose ps

查看日志:

docker compose logs

查看指定服务:

docker compose logs php

持续查看:

docker compose logs -f php

进入容器:

docker compose exec php sh

重启:

docker compose restart php

停止:

docker compose stop

删除 Compose 创建的容器和网络:

docker compose down

同时删除 Volume:

docker compose down -v

注意:

docker compose down -v

可能删除数据库数据,因此生产环境必须谨慎使用。


10. Compose 配置检查与多文件管理

实际项目中,Compose 文件很容易越来越复杂。

因此在启动之前,可以使用:

docker compose config

检查 Compose 最终解析出来的配置。

例如:

docker compose config -q

只进行配置校验。

docker compose config 会解析、合并 Compose 文件并展开变量,可以用于检查最终模型是否符合预期。

多 Compose 文件

Compose 支持:

docker compose \
  -f compose.yaml \
  -f compose.dev.yaml \
  up -d

例如:

compose.yaml
compose.dev.yaml
compose.prod.yaml

基础配置:

services:

  php:
    image: my-app:latest

开发环境覆盖:

services:

  php:
    build: .
    volumes:
      - ./:/app

生产环境则可以使用:

services:

  php:
    image: registry.example.com/my-app:1.0.0

Compose 会按照指定文件的顺序合并配置,后面的文件可以覆盖前面的配置。

这样就可以形成:

基础配置
    │
    ├── 开发环境
    │
    ├── 测试环境
    │
    └── 生产环境

而不是维护三套完全重复的 Compose 文件。


11. PHP + Nginx + MySQL + Redis 实战

下面使用一个比较典型的 PHP 项目作为完整示例。

目录:

project/
├── compose.yaml
├── Dockerfile
├── .dockerignore
├── composer.json
├── composer.lock
├── app/
├── config/
└── public/

Dockerfile

FROM php:8.3-cli

WORKDIR /app

COPY composer.json composer.lock ./

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer

RUN composer install \
    --no-dev \
    --prefer-dist \
    --no-interaction \
    --optimize-autoloader

COPY . .

EXPOSE 9501

CMD ["php", "bin/hyperf.php", "start"]

compose.yaml

services:

  php:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: app-php
    environment:
      APP_ENV: local
      DB_HOST: mysql
      DB_PORT: 3306
      DB_DATABASE: app
      DB_USERNAME: root
      DB_PASSWORD: example
      REDIS_HOST: redis
      REDIS_PORT: 6379
    ports:
      - "9501:9501"
    depends_on:
      mysql:
        condition: service_healthy
    networks:
      - app

  mysql:
    image: mysql:8.4
    container_name: app-mysql
    environment:
      MYSQL_ROOT_PASSWORD: example
      MYSQL_DATABASE: app
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test:
        - CMD
        - mysqladmin
        - ping
        - "-h"
        - localhost
      interval: 5s
      timeout: 3s
      retries: 10
    networks:
      - app

  redis:
    image: redis:7
    container_name: app-redis
    networks:
      - app

networks:
  app:

volumes:
  mysql-data:

启动:

docker compose up -d --build

查看:

docker compose ps

查看 PHP:

docker compose logs -f php

进入 PHP:

docker compose exec php sh

此时 PHP 容器访问 MySQL:

mysql:3306

访问 Redis:

redis:6379

宿主机访问 Hyperf:

localhost:9501

整个架构变成:

                   Host
                    │
                :9501
                    │
                    ▼
             ┌─────────────┐
             │     PHP     │
             │   Hyperf    │
             └──────┬──────┘
                    │
             ┌──────┴──────┐
             │             │
             ▼             ▼
        ┌─────────┐   ┌─────────┐
        │  MySQL  │   │  Redis  │
        └─────────┘   └─────────┘

              Docker Network

这就是 Compose 最典型的使用场景:

把一个由多个容器组成的应用,定义成一个整体。


12. Docker Compose 使用规范

实际项目中,建议遵循以下原则。

1. 一个 Service 尽量承担一个明确职责

例如:

php
nginx
mysql
redis

而不是把所有软件都安装到一个容器里。

2. 应用与数据库分离

应用容器:

PHP / Hyperf

数据库:

MySQL

通过 Docker Network 通信。

3. 数据库必须使用 Volume

不要依赖容器文件系统保存数据库数据。

4. 服务之间使用 Service Name 通信

例如:

mysql:3306
redis:6379

不要:

localhost:3306
localhost:6379

5. 配置与镜像分离

镜像负责:

应用程序
运行环境
依赖

Compose/环境变量负责:

环境配置
数据库地址
Redis 地址
端口
运行参数

6. 不要把生产密码直接提交到 Git

尤其避免:

MYSQL_ROOT_PASSWORD: 123456

作为真实生产配置提交。

7. 使用 docker compose config 检查最终配置

特别是存在:

.env
多个 Compose 文件
环境变量

时,非常有必要先检查最终配置。

8. 谨慎执行 down -v

开发环境可以:

docker compose down -v

但生产环境必须确认 Volume 是否包含重要数据。


13. 总结

Docker Compose 最核心的作用,就是把多个容器从“零散的 Docker 命令”变成“一份应用配置”。

它解决的是:

多个 Container
      ↓
Network
      ↓
Volume
      ↓
Environment
      ↓
Service Dependency
      ↓
统一生命周期管理

因此 Dockerfile 与 Compose 的关系可以这样理解:

                 Dockerfile
                     │
                     │ build
                     ▼
                  Image
                     │
                     │
          ┌──────────┼──────────┐
          ▼          ▼          ▼
        PHP        Nginx       Redis
          │          │          │
          └──────────┼──────────┘
                     │
              Docker Compose
                     │
             Network / Volume
                     │
                     ▼
                 Application

Dockerfile 负责“怎么构建一个应用镜像”,Compose 负责“怎么运行和组织多个应用容器”。

掌握这两个文件之后,Docker 的实际开发模式基本就清晰了:

Dockerfile
    ↓
构建应用镜像

compose.yaml
    ↓
定义应用服务

docker compose up -d
    ↓
启动完整开发环境

这也是 PHP/Hyperf 项目进入 Docker 化开发后最常见的一套基础结构。


Docker Compose 官方文档:Docker Compose
Compose 文件官方参考:Compose file reference