Docker Compose 生产级配置实战:从 Laravel 开发到部署的完整指南

Docker Compose 生产级配置实战:从 Laravel 开发到部署的完整指南

引言

在 PHP 开发领域,Laravel 凭借其优雅的语法和完善的生态成为最受欢迎的框架之一。然而,本地开发环境的搭建一直是开发者面临的痛点——PHP 版本不一致、扩展缺失、Composer 依赖冲突、MySQL 与 Redis 版本不匹配等问题层出不穷。

Docker Compose 的出现完美解决了这些问题。通过容器化技术,我们可以将整个开发环境定义为代码,实现团队成员之间、开发与生产环境之间的高度一致。本文将从一个 Laravel 项目的实际需求出发,手把手教你构建一套生产级的 Docker Compose 开发环境。

什么是 Docker Compose

Docker Compose 是 Docker 官方推出的多容器编排工具,通过一个 docker-compose.yml 文件即可定义和运行多个关联的容器。对于 Laravel 项目而言,我们通常需要以下服务:

  • Web 服务器:Nginx 或 Apache
  • PHP-FPM:运行 PHP 代码
  • MySQL:关系数据库
  • Redis:缓存和队列
  • Node.js:前端资源编译(Vite、Mix)

使用 Docker Compose 后,只需要一条 docker-compose up 命令就能启动整个开发环境,彻底告别"在我机器上能跑"的尴尬。

项目结构规划

在开始之前,我们先规划好项目目录结构:

laravel-project/
├── docker/
│   ├── php/
│   │   └── Dockerfile
│   ├── nginx/
│   │   └── default.conf
│   └── mysql/
│       └── init.sql
├── src/                    # Laravel 项目代码
├── docker-compose.yml
├── docker-compose.prod.yml
└── .env

将 Docker 相关文件放在独立的 docker/ 目录下,保持项目根目录整洁。Laravel 代码放在 src/ 目录中。

编写 Dockerfile

PHP-FPM 镜像

PHP 官方镜像不包含 Laravel 所需的扩展,我们需要自定义 Dockerfile:

# docker/php/Dockerfile
FROM php:8.3-fpm

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    git \
    unzip \
    libpq-dev \
    libzip-dev \
    libpng-dev \
    libonig-dev \
    && rm -rf /var/lib/apt/lists/*

# 安装 PHP 扩展
RUN docker-php-ext-install \
    pdo_mysql \
    mbstring \
    zip \
    gd \
    bcmath

# 安装 Redis 扩展
RUN pecl install redis && docker-php-ext-enable redis

# 安装 Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# 设置工作目录
WORKDIR /var/www

# 创建非 root 用户
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /var/www
USER appuser

这里有几个关键点:

  • 使用 docker-php-ext-install 安装 PHP 扩展,这是官方镜像提供的便捷工具
  • 通过 pecl 安装 Redis 扩展,Laravel 大量使用 Redis 做缓存和队列
  • 创建非 root 用户运行 PHP-FPM,提升安全性

Nginx 配置

# docker/nginx/default.conf
server {
    listen 80;
    server_name localhost;
    root /var/www/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

注意 fastcgi_pass php:9000 中的 php 是 Docker Compose 中的服务名称,Docker 内部 DNS 会自动解析。

docker-compose.yml 详解

基础配置

# docker-compose.yml
version: '3.8'

services:
  php:
    build:
      context: ./docker/php
      dockerfile: Dockerfile
    container_name: laravel-php
    volumes:
      - ./src:/var/www
    environment:
      - APP_ENV=local
      - DB_CONNECTION=mysql
      - DB_HOST=mysql
      - DB_PORT=3306
      - DB_DATABASE=laravel
      - DB_USERNAME=laravel
      - DB_PASSWORD=secret
      - REDIS_HOST=redis
    networks:
      - app-network
    depends_on:
      mysql:
        condition: service_healthy

  nginx:
    image: nginx:alpine
    container_name: laravel-nginx
    ports:
      - "8080:80"
    volumes:
      - ./src:/var/www
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    networks:
      - app-network
    depends_on:
      - php

  mysql:
    image: mysql:8.0
    container_name: laravel-mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: laravel
      MYSQL_USER: laravel
      MYSQL_PASSWORD: secret
    volumes:
      - mysql_data:/var/lib/mysql
      - ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      timeout: 20s
      retries: 10

  redis:
    image: redis:7-alpine
    container_name: laravel-redis
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - app-network
    command: redis-server --appendonly yes

  node:
    image: node:20-alpine
    container_name: laravel-node
    working_dir: /var/www
    volumes:
      - ./src:/var/www
    command: >
      sh -c "npm install && npm run dev"
    networks:
      - app-network

volumes:
  mysql_data:
  redis_data:

networks:
  app-network:
    driver: bridge

配置要点说明

1. depends_on 与健康检查

MySQL 容器启动需要时间,直接在 depends_on 中加上还不够——Docker 只会等待容器启动,不会等待 MySQL 就绪。通过 healthcheck 机制真正等数据库可用后再启动 PHP:

depends_on:
  mysql:
    condition: service_healthy

2. 数据持久化

使用命名卷 mysql_dataredis_data 存储数据库和缓存数据,这样即使容器删除重建,数据也不会丢失。命名卷由 Docker 管理,比绑定挂载性能更好且跨平台兼容。

3. 网络隔离

创建一个自定义网络 app-network,所有服务加入同一网络,服务之间通过名称通信(如 php 访问 mysql:3306),外部则只能通过映射端口访问。

4. Node 容器处理前端资源

Node 容器运行 npm run dev 进行前端资源编译,通过 volume 共享代码,将编译结果直接输出到 Laravel 的 public/ 目录。

生产环境配置

生产环境与开发环境有显著差异——不需要 Node 容器、不需要源码挂载、需要反向代理和 SSL。我们通过 docker-compose.prod.yml 来覆盖开发配置:

# docker-compose.prod.yml
version: '3.8'

services:
  php:
    build:
      context: ./docker/php
      dockerfile: Dockerfile.prod
    volumes: []  # 不挂载源码
    environment:
      - APP_ENV=production
      - APP_DEBUG=false

  nginx:
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./docker/nginx/ssl:/etc/nginx/ssl

  mysql:
    ports: []  # 不暴露数据库端口到外部

  node: {}  # 生产环境不需要 Node 容器

部署时使用配置文件合并:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

如果需要构建前端资源后再部署,可以在 CI/CD 流程中先运行 npm run build,然后将构建产物复制到镜像中。

日常开发工作流

首次启动

# 克隆项目
git clone git@example.com:project.git && cd project

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

# 安装 Composer 依赖
docker-compose exec php composer install

# 生成应用密钥
docker-compose exec php php artisan key:generate

# 运行数据库迁移
docker-compose exec php php artisan migrate

# 生成 IDE 辅助文件(可选)
docker-compose exec php php artisan ide-helper:generate

常用命令

# 进入 PHP 容器
docker-compose exec php bash

# 查看日志
docker-compose logs -f php

# 重启某个服务
docker-compose restart nginx

# 重建容器(修改 Dockerfile 后)
docker-compose up -d --build php

# 停止并删除所有容器
docker-compose down

# 停止并删除容器和卷(⚠️ 会删除数据库数据)
docker-compose down -v

Xdebug 调试配置

docker/php/Dockerfile 中添加 Xdebug:

RUN pecl install xdebug && docker-php-ext-enable xdebug

COPY docker/php/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini

xdebug.ini 内容:

xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
xdebug.idekey=VSCODE

Windows/Mac 上使用 host.docker.internal 访问宿主机,Linux 需要替换为宿主机 IP。

然后在 VS Code 中添加调试配置:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Laravel Docker",
      "type": "php",
      "request": "launch",
      "port": 9003,
      "pathMappings": {
        "/var/www": "${workspaceFolder}/src"
      }
    }
  ]
}

性能优化技巧

1. 使用 Mutagen 加速文件同步

在 Docker Desktop 中,macOS 和 Windows 的文件绑定挂载性能很差,因为需要经过 Hyper-V/WSL2 的文件系统转换。使用 Mutagen 可以大幅提升性能:

# 安装 Mutagen
brew install mutagen-io/mutagen/mutagen

# 创建 mutagen.yml
mutagen:
  sync:
    php-sync:
      alpha: "./src"
      beta: "docker://laravel-php/var/www"
      mode: "two-way-resolved"

2. 优化镜像大小

  • 使用 alpine 基础镜像,减少体积
  • 合理利用 Docker 层缓存:将不常变化的指令(系统依赖安装)放在前面
  • 多阶段构建:编译阶段和运行阶段分离

3. Composer 依赖缓存

docker/php/Dockerfile 中提前安装 Composer 依赖,利用 Docker 层缓存加速构建:

COPY composer.json composer.lock /var/www/
RUN composer install --no-dev --no-scripts
COPY . /var/www/
RUN composer install --no-dev

常见问题与解决方案

1. 端口冲突

如果本地已运行 MySQL 或 Nginx,Docker 容器端口会冲突。解决方案:

  • 修改映射端口:"3307:3306" 将宿主机 3307 映射到容器 3306
  • 或停止本地服务:net stop mysql80

2. 权限问题

Linux 上运行 Docker 时,容器内创建的文件可能属于 root 用户。解决方案在 Dockerfile 中已体现——使用 appuser 运行 PHP,确保 UID 与宿主机一致。

如果遇到权限问题,可以手动修复:

docker-compose exec php bash -c "chown -R appuser:appuser /var/www/storage"

3. 容器启动顺序

PHP 容器可能比 MySQL 先启动完毕,导致 "Connection refused" 错误。除了 healthcheck,也可以在 Laravel 代码层面重试连接:

// config/database.php
'mysql' => [
    'driver' => 'mysql',
    // ...
    'options' => extension_loaded('pdo_mysql') ? [
        PDO::ATTR_EMULATE_PREPARES => true,
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    ] : [],
],

或者在 PHP 容器启动脚本中添加等待逻辑,但 healthcheck 是最优雅的方式。

总结

本文从零开始构建了一套 Laravel 项目的 Docker Compose 开发环境,涵盖 PHP-FPM、Nginx、MySQL、Redis 和 Node.js 容器配置,并延伸到了生产环境部署、Xdebug 调试和性能优化。

使用 Docker Compose 管理 Laravel 开发环境带来的核心收益是环境一致性——新成员加入项目只需运行 docker-compose up 即可开始编码,彻底告别环境配置的烦恼。同时,将基础设施定义为代码,也便于版本控制和团队协作。

建议将这个 docker-compose.yml 提交到 Git 仓库,让每个团队成员都使用相同的标准化环境。随着项目复杂度提升,你还可以在此基础上加入 Elasticsearch、RabbitMQ 等服务,构建更完善的本地开发集群。