Docker Compose 完全指南:从基础到实践

一、Docker Compose 基础认知

1.1 核心概念

  • Compose 文件:使用 YAML 格式编写的配置文件(默认名称为 docker-compose.yml),定义了应用所需的所有服务、网络、数据卷等资源。
  • 服务(Service):构成应用的单个容器实例(如 Web 服务、数据库服务、缓存服务),在配置文件中通过 services 节点定义,可指定镜像、端口映射、环境变量等参数。
  • 项目(Project):由 Compose 管理的一组关联服务的集合,一个项目对应一个应用,通过 docker-compose 命令对项目整体进行启动、停止、重启等操作。
  • 网络(Network):Compose 会自动为项目创建默认网络,所有服务容器默认加入该网络,实现容器间的相互通信(可通过服务名直接访问)。
  • 数据卷(Volume):用于持久化容器数据,避免容器删除后数据丢失,支持容器间数据共享,在配置文件中通过 volumes 节点定义。

1.2 适用场景

  • 开发环境:快速搭建包含多服务的本地开发环境(如 Web 服务 + MySQL + Redis),保证团队成员开发环境一致性。
  • 测试环境:一键部署完整的应用栈,用于自动化测试或手动测试,降低环境配置成本。
  • 小型生产环境:对于服务规模较小、架构简单的应用,可直接使用 Compose 进行部署(大型生产环境更推荐 Kubernetes)。

1.3 安装前提

  • 已安装 Docker Engine(Compose 依赖 Docker 运行,需确保 Docker 正常工作)。

  • 安装 Docker Compose:

    • Linux 系统:通过命令直接下载(以 v2.24.5 版本为例):

      sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
      sudo chmod +x /usr/local/bin/docker-compose
      
      
      
      
  • Windows/macOS 系统:安装 Docker Desktop 后会自动附带 Docker Compose,无需额外安装。

1.4 验证安装

执行以下命令,若输出 Compose 版本信息,则说明安装成功:

docker-compose --version
# 示例输出:docker-compose version v2.24.5, build 1234567

二、Docker Compose 核心命令

Docker Compose 命令通过 docker-compose [选项] [命令] [参数] 格式执行,以下是最常用的命令:

命令作用常用选项示例
up构建并启动所有服务容器(默认前台运行)-d:后台运行;--build:启动前重新构建镜像;--force-recreate:强制重新创建容器docker-compose up -d(后台启动所有服务)
down停止并删除所有服务容器、网络(默认不删除数据卷)-v:同时删除数据卷;--rmi all:删除项目相关的所有镜像docker-compose down -v(停止并删除容器、网络、数据卷)
ps查看当前项目的所有服务容器状态-a:显示所有容器(包括已停止的)docker-compose ps -a
start启动已停止的服务容器无常用选项docker-compose start web(仅启动 web 服务)
stop停止运行中的服务容器(不删除容器)无常用选项docker-compose stop db(仅停止 db 服务)
restart重启服务容器无常用选项docker-compose restart(重启所有服务)
logs查看服务容器的日志输出-f:实时跟踪日志;--tail 100:显示最后 100 行日志docker-compose logs -f web(实时查看 web 服务日志)
exec进入运行中的服务容器执行命令-it:交互式终端(必备)docker-compose exec -it db bash(进入 db 容器的 bash 终端)
build构建或重新构建服务的自定义镜像--no-cache:构建时不使用缓存docker-compose build web(仅构建 web 服务的镜像)

三、docker-compose.yml 配置语法详解

docker-compose.yml 是 Compose 的核心配置文件,遵循 YAML 语法(缩进敏感,建议使用 2 个空格缩进),以下是完整的配置结构和关键节点说明:

3.1 配置结构总览

# 1. 版本声明(必须在第一行,指定 Compose 文件格式版本,需与 Docker Compose 版本兼容)
version: "3.8"  # 推荐使用 3.x 系列(3.0+ 支持大部分新特性)

# 2. 服务定义(核心节点,所有容器服务在此配置)
services:
  服务1名称:  # 自定义服务名(如 web、db、redis)
    # 2.1 镜像相关配置
    image: nginx:1.23  # 直接使用官方镜像(格式:镜像名:标签)
    # build: ./web  # 若需自定义镜像,指定 Dockerfile 所在目录(与 image 二选一,优先用 build)
    # build:
    #   context: ./web  # Dockerfile 所在上下文目录
    #   dockerfile: Dockerfile.dev  # 自定义 Dockerfile 文件名(默认 Dockerfile)
    #   args:  # 传递给 Dockerfile 的构建参数(仅在 build 时生效)
    #     - ENV=development

    # 2.2 容器名称配置
    container_name: my-web  # 自定义容器名(默认格式:项目名_服务名_序号)

    # 2.3 端口映射(宿主机端口:容器内端口)
    ports:
      - "8080:80"  # 宿主机 8080 端口映射到容器 80 端口(TCP 协议默认)
      - "443:443/udp"  # 指定 UDP 协议

    # 2.4 环境变量配置(两种方式:直接指定或从文件读取)
    environment:
      - MYSQL_ROOT_PASSWORD=123456  # 直接设置环境变量
      - REDIS_PORT=6379
    env_file:  # 从文件读取环境变量(文件格式:KEY=VALUE,每行一个)
      - ./.env  # 项目根目录的 .env 文件
      - ./db.env  # 数据库相关的环境变量文件

    # 2.5 数据卷配置(持久化数据或共享文件)
    volumes:
      - ./html:/usr/share/nginx/html  # 宿主机目录 ./html 挂载到容器目录
      - nginx-log:/var/log/nginx  # 命名数据卷 nginx-log 挂载到容器目录
      - ./conf/nginx.conf:/etc/nginx/nginx.conf:ro  # ro:只读权限(默认 rw 可读写)

    # 2.6 网络配置(指定容器加入的网络)
    networks:
      - my-network  # 加入自定义网络 my-network(需在 networks 节点定义)

    # 2.7 依赖关系(控制服务启动顺序,仅保证启动顺序,不保证服务就绪)
    depends_on:
      - db  # 启动当前服务前,先启动 db 服务
      - redis  # 启动当前服务前,先启动 redis 服务

    # 2.8 重启策略(容器退出后的重启规则)
    restart: always  # 总是重启(无论退出码是什么)
    # restart: on-failure  # 仅在容器退出码非 0 时重启
    # restart: unless-stopped  # 除非手动停止,否则总是重启

    # 2.9 健康检查(检测服务是否正常运行)
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/"]  # 检测命令(访问 80 端口)
      interval: 30s  # 每 30 秒检查一次
      timeout: 10s  # 检查超时时间(超过 10 秒视为失败)
      retries: 3  # 连续 3 次失败视为服务不健康
      start_period: 60s  # 服务启动后,等待 60 秒再开始健康检查

  服务2名称:  # 如 db(数据库服务)
    image: mysql:8.0
    container_name: my-db
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=123456
      - MYSQL_DATABASE=mydb  # 初始化时创建的数据库名
    volumes:
      - mysql-data:/var/lib/mysql  # 命名数据卷(持久化 MySQL 数据)
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql  # 初始化 SQL 脚本(MySQL 镜像特有)
    networks:
      - my-network
    restart: always

# 3. 数据卷定义(所有服务可共享的命名数据卷)
volumes:
  nginx-log:  # 命名数据卷 1(无需指定路径,Docker 自动管理)
  mysql-data:  # 命名数据卷 2(持久化 MySQL 数据,容器删除后数据不丢失)

# 4. 网络定义(自定义网络,实现服务间隔离或通信)
networks:
  my-network:  # 自定义桥接网络(默认驱动为 bridge)
    driver: bridge  # 网络驱动类型(bridge、overlay 等,默认 bridge)

3.2 关键配置说明

(1)version 版本兼容性

version 字段指定 Compose 文件格式版本,需与 Docker Compose 版本匹配,避免出现语法不支持的问题:

  • Compose V2.0+ 支持 3.0 至 3.9 版本的配置文件;
  • 若使用较新特性(如 healthcheck 的 start_period),建议使用 3.4+ 版本。
(2)volumes 数据卷类型

配置文件中 volumes 有两种使用方式:

  • 宿主机目录挂载:格式 宿主机路径:容器内路径[:权限],如 ./html:/usr/share/nginx/html,适合开发环境(实时同步代码);
  • 命名数据卷:格式 数据卷名:容器内路径,如 mysql-data:/var/lib/mysql,适合生产环境(Docker 统一管理数据,避免宿主机路径依赖)。
(3)depends_on 启动顺序

depends_on 仅保证服务的启动顺序(如先启动 db 再启动 web),但不保证 db 服务 “完全就绪”(如 MySQL 启动后需 10 秒才能接受连接)。若需等待服务就绪,需在应用代码中添加重试逻辑(如 Web 服务连接 MySQL 失败时重试)。

(4)env_file 环境变量文件

环境变量文件需满足 KEY=VALUE 格式,每行一个变量,示例(.env 文件):

# .env 文件
REDIS_HOST=redis
REDIS_PORT=6379
APP_ENV=production

四、实战示例:多服务应用部署

以下通过 2 个典型示例,演示 Docker Compose 的实际应用。

示例 1:Nginx + PHP 网站(LNMP 简化版)

需求:部署一个 PHP 网站,Nginx 作为反向代理,PHP-FPM 处理 PHP 脚本,数据卷持久化网站代码。

1. 项目结构
lnmp-demo/
├── docker-compose.yml  # Compose 配置文件
├── nginx/
│   ├── nginx.conf      # Nginx 配置文件
│   └── conf.d/
│       └── default.conf  # 网站虚拟主机配置
├── php/
│   └── Dockerfile      # PHP-FPM 自定义镜像(安装必要扩展)
└── www/
    └── index.php       # PHP 网站代码
2. 配置文件编写
(1)docker-compose.yml
version: "3.8"

services:
  # Nginx 服务
  nginx:
    image: nginx:1.23
    container_name: lnmp-nginx
    ports:
      - "80:80"  # 宿主机 80 端口映射到 Nginx 80 端口
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro  # Nginx 主配置(只读)
      - ./nginx/conf.d:/etc/nginx/conf.d:ro          # 虚拟主机配置(只读)
      - ./www:/usr/share/nginx/html                  # 网站代码目录(持久化)
      - nginx-log:/var/log/nginx                     # Nginx 日志数据卷
    depends_on:
      - php  # 先启动 PHP-FPM 服务
    networks:
      - lnmp-network
    restart: always

  # PHP-FPM 服务
  php:
    build: ./php  # 基于 ./php/Dockerfile 构建镜像
    container_name: lnmp-php
    volumes:
      - ./www:/usr/share/nginx/html  # 与 Nginx 共享网站代码目录
    networks:
      - lnmp-network
    restart: always

# 数据卷定义
volumes:
  nginx-log:  # 持久化 Nginx 日志

# 网络定义
networks:
  lnmp-network:
    driver: bridge
(2)PHP Dockerfile(./php/Dockerfile)
# 基础镜像(PHP-FPM 8.2 版本,基于 Debian)
FROM php:8.2-fpm

# 安装常用扩展(如 mysqli、pdo_mysql)
RUN docker-php-ext-install mysqli pdo_mysql

# 暴露 PHP-FPM 默认端口(9000)
EXPOSE 9000

# 启动 PHP-FPM 服务(前台运行,确保容器不退出)
CMD ["php-fpm"]
(3)Nginx 虚拟主机配置(./nginx/conf.d/default.conf)
server {
    listen 80;
    server_name localhost;  # 网站域名(本地测试用 localhost)
    root /usr/share/nginx/html;  # 网站根目录(与 PHP 共享的目录)
    index index.php index.html;  # 默认首页

    # 处理 PHP 请求:转发到 PHP-FPM 服务(服务名即 "php")
    location ~ \.php$ {
        fastcgi_pass   php:9000;  # PHP 服务名:端口(Compose 内部可通过服务名访问)
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }

    # 静态文件缓存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 30d;
    }
}
(4)PHP 测试代码(./www/index.php)
<?php
phpinfo();  // 输出 PHP 环境信息
?>
3. 启动与验证
  1. 进入项目根目录(lnmp-demo/);

  2. 构建并启动服务:

docker-compose up -d --build # --build 确保 PHP 镜像重新构建


3. 验证:打开浏览器访问 `http://localhost`,若显示 PHP 信息页面,则部署成功;

4. 查看日志(可选):

```bash
docker-compose logs -f nginx  # 实时查看 Nginx 日志

示例 2:Spring Boot + MySQL 后端服务

需求:部署一个 Spring Boot 后端应用,连接 MySQL 数据库,数据卷持久化数据库数据和应用配置。

1. 项目结构
springboot-mysql-demo/
├── docker-compose.yml          # Docker Compose 配置文件
├── backend/                    # Spring Boot 后端服务
│   ├── Dockerfile              # 后端服务 Dockerfile
│   ├── pom.xml                 # Maven 依赖配置
│   └── src/                    # 源代码目录
└── mysql/                      # MySQL 相关配置
    └── init.sql                # 数据库初始化脚本
2. 配置文件编写
1. spring boot 后端服务
(1)backend/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>
    
    <groupId>com.example</groupId>
    <artifactId>springboot-mysql-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-mysql-demo</name>
    <description>Demo project for Spring Boot with MySQL</description>
    
    <properties>
        <java.version>17</java.version>
    </properties>
    
    <dependencies>
        <!-- Spring Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Spring Data JPA -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        
        <!-- MySQL Connector -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- Lombok (简化代码) -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- Spring Boot Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
(2)/SpringbootMysqlDemoApplication.java

backend/src/main/java/com/example/demo/SpringbootMysqlDemoApplication.java

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringbootMysqlDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootMysqlDemoApplication.class, args);
    }
}
(3)/User.java

backend/src/main/java/com/example/demo/model/User.java

package com.example.demo.model;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
    
    @Column(nullable = false, unique = true)
    private String email;
}
(4)/UserRepository.java

backend/src/main/java/com/example/demo/repository/UserRepository.java

package com.example.demo.repository;

import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
(5)/UserController.java

backend/src/main/java/com/example/demo/controller/UserController.java

package com.example.demo.controller;

import com.example.demo.model.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserRepository userRepository;

    // 获取所有用户
    @GetMapping
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    // 创建新用户
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User savedUser = userRepository.save(user);
        return new ResponseEntity<>(savedUser, HttpStatus.CREATED);
    }

    // 获取单个用户
    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        return userRepository.findById(id)
                .map(ResponseEntity::ok)
                .orElse(ResponseEntity.notFound().build());
    }

    // 更新用户
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
        return userRepository.findById(id)
                .map(user -> {
                    user.setName(userDetails.getName());
                    user.setEmail(userDetails.getEmail());
                    User updatedUser = userRepository.save(user);
                    return ResponseEntity.ok(updatedUser);
                })
                .orElse(ResponseEntity.notFound().build());
    }

    // 删除用户
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        return userRepository.findById(id)
                .map(user -> {
                    userRepository.delete(user);
                    return ResponseEntity.noContent().build();
                })
                .orElse(ResponseEntity.notFound().build());
    }
}
(6)/application.properties

backend/src/main/resources/application.properties

# 服务器端口
server.port=8080

# 数据库配置(值将通过环境变量注入)
spring.datasource.url=jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:demo_db}?useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=${DB_USERNAME:root}
spring.datasource.password=${DB_PASSWORD:password}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.format_sql=true
(7)backend/Dockerfile
# 构建阶段
FROM maven:3.8.5-openjdk-17 AS build
WORKDIR /app
COPY pom.xml .
COPY src ./src
RUN mvn clean package -DskipTests

# 运行阶段
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
2. MySQL 数据库配置
mysql/init.sql
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS demo_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 使用创建的数据库
USE demo_db;

-- 这里可以添加初始数据
-- INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com');
3. Docker Compose 配置
(1)docker-compose.yml
version: '3.8'

services:
  # MySQL 数据库服务
  mysql:
    image: mysql:8.0
    container_name: mysql-db
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-password}
      MYSQL_DATABASE: ${DB_NAME:-demo_db}
      MYSQL_USER: ${DB_USERNAME:-appuser}
      MYSQL_PASSWORD: ${DB_PASSWORD:-password}
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql  # 数据持久化
      - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql  # 初始化脚本
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Spring Boot 后端服务
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    container_name: springboot-app
    restart: always
    depends_on:
      mysql:
        condition: service_healthy  # 等待数据库健康检查通过后再启动
    environment:
      DB_HOST: mysql  # 对应 MySQL 服务名
      DB_PORT: 3306
      DB_NAME: ${DB_NAME:-demo_db}
      DB_USERNAME: ${DB_USERNAME:-appuser}
      DB_PASSWORD: ${DB_PASSWORD:-password}
    ports:
      - "8080:8080"
    networks:
      - app-network

# 自定义网络(服务间通信)
networks:
  app-network:
    driver: bridge

# 数据卷(持久化数据)
volumes:
  mysql-data:
4. 部署与运行步骤
(1). 构建和启动服务
# 在项目根目录执行
docker compose up -d
(2). 查看服务状态
docker compose ps
(3). 查看日志
# 查看后端服务日志
docker compose logs -f backend

# 查看数据库服务日志
docker compose logs -f mysql
(4). 测试 API

服务启动后,可以通过以下端点测试 API:

  • 获取所有用户:GET http://localhost:8080/api/users

  • 创建用户:POST http://localhost:8080/api/users,请求体:

    {
      "name": "John Doe",
      "email": "john@example.com"
    }
    
  • 获取指定用户:GET http://localhost:8080/api/users/{id}

  • 更新用户:PUT http://localhost:8080/api/users/{id}

  • 删除用户:DELETE http://localhost:8080/api/users/{id}

(5). 停止服务
# 停止服务但不删除数据
docker compose down

# 停止服务并删除数据卷(谨慎使用,会清除所有数据)
docker compose down -v
5. 关键特性说明
  1. 服务依赖管理:通过 depends_on 和健康检查确保数据库准备就绪后才启动后端服务
  2. 数据持久化:使用 Docker 数据卷 mysql-data 保存 MySQL 数据,容器重启数据不丢失
  3. 环境变量配置:敏感信息通过环境变量注入,避免硬编码
  4. 网络隔离:自定义网络 app-network 确保服务间安全通信
  5. 自动重启:配置 restart: always 确保服务故障后自动恢复
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐