在 PHP 开发领域,Laravel 凭借其优雅的语法和完善的生态成为最受欢迎的框架之一。然而,本地开发环境的搭建一直是开发者面临的痛点——PHP 版本不一致、扩展缺失、Composer 依赖冲突、MySQL 与 Redis 版本不匹配等问题层出不穷。
Docker Compose 的出现完美解决了这些问题。通过容器化技术,我们可以将整个开发环境定义为代码,实现团队成员之间、开发与生产环境之间的高度一致。本文将从一个 Laravel 项目的实际需求出发,手把手教你构建一套生产级的 Docker Compose 开发环境。
Docker Compose 是 Docker 官方推出的多容器编排工具,通过一个 docker-compose.yml 文件即可定义和运行多个关联的容器。对于 Laravel 项目而言,我们通常需要以下服务:
使用 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/ 目录中。
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 做缓存和队列# 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
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_data 和 redis_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
在 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"
}
}
]
}
在 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"
alpine 基础镜像,减少体积在 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
如果本地已运行 MySQL 或 Nginx,Docker 容器端口会冲突。解决方案:
"3307:3306" 将宿主机 3307 映射到容器 3306net stop mysql80Linux 上运行 Docker 时,容器内创建的文件可能属于 root 用户。解决方案在 Dockerfile 中已体现——使用 appuser 运行 PHP,确保 UID 与宿主机一致。
如果遇到权限问题,可以手动修复:
docker-compose exec php bash -c "chown -R appuser:appuser /var/www/storage"
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 等服务,构建更完善的本地开发集群。