切换语言
切换主题

Docker Compose一键部署PHP环境:DNMP完整教程(Nginx+MySQL+PHP)

Docker Compose部署PHP开发环境示意图

凌晨两点。盯着终端里滚动的报错信息,我猛地一拍桌子。

“Nginx找不到PHP-FPM的socket文件”——这已经是今晚第8个报错了。手动装LNMP环境,我改了nginx.conf、php-fpm.conf、my.cnf三个配置文件,重启了十几次服务。窗外天都快亮了,环境还是跑不起来。

更崩溃的是,隔壁工位的老王说:“我电脑上跑得好好的啊,你是不是PHP版本不对?”

好家伙,他用PHP 7.4,我装的是8.1。MySQL他是5.7,我手滑装成了8.0。难怪代码跑不起来。

说实话,这种场景你肯定也遇到过。团队里每个人的开发环境都不一样,新人入职光配环境就要半天,老员工还得一对一指导。代码在A电脑上正常,到B电脑就炸了。部署到服务器?那更是碰运气。

直到我试了Docker Compose一键部署DNMP环境,这些问题才彻底消失。10分钟搞定Nginx、MySQL、PHP、Redis全家桶,团队所有人用同一份配置文件,再也没人说”我电脑上能跑”了。

为什么选择Docker Compose部署PHP环境

传统LNMP部署的三大痛点

痛点1:手动安装配置繁琐,一步错步步错

还记得第一次装LNMP的场景吗?先apt-get install nginx,再装php-fpm,然后装MySQL。装完还得改配置文件,nginx.conf要配location规则,php.ini要开扩展,my.cnf要调参数。

三个配置文件散落在不同目录:/etc/nginx//etc/php//etc/mysql/。一个配置写错了,服务就启动不了。排查问题得翻日志,Nginx日志、PHP-FPM日志、MySQL日志,看到头晕。

我见过有人因为php-fpm.conf里的listen = 127.0.0.1:9000写成了listen = /run/php/php7.4-fpm.sock,Nginx找不到PHP,排查了三个小时。

痛点2:版本兼容性问题让人头秃

团队里总有那么几个”版本钉子户”。老项目跑在PHP 5.6上,新项目要用PHP 8.1。MySQL 5.7的GROUP BY语法到MySQL 8.0就报错,因为8.0默认启用了ONLY_FULL_GROUP_BY模式。

更可怕的是,不同成员电脑上的版本五花八门:

  • 小李的Mac装的是PHP 7.4 + MySQL 5.7
  • 老王的Ubuntu是PHP 8.0 + MySQL 8.0
  • 新来的实习生Windows装的是PHP 8.1 + MariaDB 10.6

代码在小李电脑上跑得好好的,推到Git上,老王拉下来就报错。测试环境又是另一套版本,线上生产环境版本还不一样。这不是找死吗?

痛点3:团队协作效率低到怀疑人生

新人入职第一天,老员工得花半天时间手把手教他装环境:

  1. 安装Nginx(20分钟)
  2. 安装PHP和扩展(30分钟,还得编译)
  3. 安装MySQL(15分钟)
  4. 配置三个服务互相通信(1小时,各种踩坑)
  5. 导入测试数据(10分钟)

运气好的话,下午能开始写代码。运气不好?那就明天再说吧。

我之前在一家创业公司,因为环境不一致,一个简单的用户注册功能,开发环境测试通过,上线后报”数据库连接失败”。原因是开发环境MySQL用户名root没密码,生产环境有密码,配置文件忘了改。凌晨紧急回滚,老板差点骂娘。

Docker Compose的三大优势

优势1:配置即代码,一份文件全团队共享

Docker Compose把所有配置都写进一个docker-compose.yml文件:

  • 用什么版本的Nginx?(nginx:1.25-alpine)
  • PHP装哪些扩展?(mysqli、pdo_mysql、redis)
  • MySQL配置参数是啥?(my.cnf统一管理)
  • 服务之间怎么通信?(自动DNS解析)

这个文件往Git一扔,团队所有人拉下来都是同一套环境。新人入职?clone项目,docker-compose up -d一行命令,喝杯咖啡回来环境就跑起来了。

不用再问”你MySQL密码是多少?""你php.ini在哪?""你Nginx配置给我看看”。一个文件搞定所有问题。

优势2:一键启动/销毁,干净利落

启动所有服务:

docker-compose up -d

停止并删除所有容器:

docker-compose down

想切换PHP版本?改一行配置image: php:7.4-fpm变成image: php:8.1-fpm,重新up一下,搞定。

不会像传统方式那样,卸载旧版本PHP还得清理一堆残留文件,生怕删错东西导致系统炸掉。Docker容器之间完全隔离,删了就删了,干干净净。

优势3:环境隔离和多版本共存

想同时跑PHP 7.4和PHP 8.1?传统方式得装两套PHP,配置两个不同端口,改Nginx配置指定不同的fastcgi_pass。麻烦死了。

用Docker Compose?在配置文件里定义两个PHP服务:

php74:
  image: php:7.4-fpm

php81:
  image: php:8.1-fpm

Nginx配置里想用哪个版本,就指向哪个服务名。老项目继续用php74,新项目用php81,互不干扰。

GitHub上star最多的DNMP项目(imeepo/dnmp)有5000+star,说明这套方案已经被大量团队验证过了。不是什么新鲜玩意儿,是成熟可靠的解决方案。

DNMP架构设计详解

什么是DNMP

DNMP其实就是LNMP的Docker版本,四个字母各代表一个组件:

  • D = Docker:容器化平台,把每个服务装进独立的”集装箱”
  • N = Nginx:Web服务器,接收HTTP请求,转发给PHP处理
  • M = MySQL:关系型数据库,存储数据(也可以换成MariaDB、PostgreSQL)
  • P = PHP:PHP-FPM运行环境,执行PHP代码

除了这四个核心组件,一般还会加上:

  • Redis:缓存服务,加速数据读取
  • PHPMyAdmin:数据库管理工具,可视化操作MySQL

这些服务在传统LNMP里是直接装在系统上的,混在一起。DNMP把它们分别装进Docker容器,就像把不同的软件装进不同的虚拟机里,但比虚拟机轻量得多。

服务编排架构

想象一下,你要组装一套音响系统:功放、音箱、播放器。每个设备独立工作,但需要线缆连接才能协同发声。

DNMP也是这个道理。Nginx、PHP、MySQL是三个独立的容器,但需要通过Docker网络连接:

请求处理流程:

  1. 浏览器发送HTTP请求到 localhost:80
  2. Nginx容器接收请求,发现是PHP文件
  3. Nginx通过9000端口转发给PHP容器(通过服务名php)
  4. PHP容器执行代码,需要查数据库时连接MySQL容器(通过服务名mysql)
  5. PHP返回结果给Nginx
  6. Nginx把HTML返回给浏览器

通信机制:
Docker Compose会自动创建一个桥接网络(bridge network),把所有服务连进去。每个服务都有自己的服务名,Docker自动做DNS解析。

比如PHP要连MySQL,不用写127.0.0.1:3306,直接写mysql:3306就行。Docker会自动把mysql解析成MySQL容器的IP地址。超方便。

端口映射:

  • Nginx容器:80端口 → 宿主机80端口(访问 localhost就能看到网站)
  • MySQL容器:3306端口 → 宿主机3306端口(可以用Navicat等工具连接)
  • PHPMyAdmin容器:80端口 → 宿主机8080端口(访问 localhost:8080管理数据库)
  • Redis容器:6379端口 → 宿主机6379端口

卷挂载(Volume):
容器删了,数据就没了?不会的。通过卷挂载,把容器里的重要目录映射到宿主机:

  • ./www → Nginx和PHP容器的 /var/www/html(代码目录)
  • ./mysql/data → MySQL容器的 /var/lib/mysql(数据库文件)
  • ./nginx/logs → Nginx容器的 /var/log/nginx(访问日志)

这样即使容器删了重建,数据还在宿主机上,不会丢。

目录结构设计

标准的DNMP项目目录长这样:

dnmp/
├── docker-compose.yml       # 核心编排文件,定义所有服务
├── .env                     # 环境变量配置(密码、端口等)
├── .gitignore               # Git忽略文件(.env不上传)
├── nginx/
│   ├── conf.d/
│   │   └── default.conf    # 站点配置(根目录、PHP转发规则)
│   └── logs/               # 访问日志和错误日志
│       ├── access.log
│       └── error.log
├── php/
│   ├── Dockerfile          # PHP镜像定制(安装扩展)
│   ├── php.ini             # PHP配置(内存限制、上传大小)
│   └── php-fpm.conf        # PHP-FPM配置(进程数)
├── mysql/
│   ├── data/               # 数据库文件持久化目录
│   └── my.cnf              # MySQL配置(字符集、最大连接数)
└── www/                    # 项目代码目录
    └── index.php           # 测试文件

关键文件说明:

  • docker-compose.yml:最核心的文件,定义用哪个镜像、挂载哪些目录、映射哪些端口
  • .env:环境变量,比如MYSQL_ROOT_PASSWORD=123456,敏感信息单独管理
  • nginx/conf.d/default.conf:Nginx站点配置,设置网站根目录、PHP转发规则
  • php/Dockerfile:基于官方PHP镜像,额外安装mysqli、redis等扩展
  • mysql/data/:数据库文件存这里,容器删了重建,数据还在

这套结构看起来文件挺多,但每个文件职责清晰。不像传统LNMP配置散落各处,找起来头疼。

10分钟实战:从零搭建DNMP环境

前置准备

1. 安装Docker

  • Mac/Windows:下载Docker Desktop,安装就完事了,自带docker-compose
  • Linux(Ubuntu示例)
    sudo apt update
    sudo apt install docker.io docker-compose -y
    sudo systemctl start docker
    sudo systemctl enable docker

2. 验证安装

docker --version
# 输出:Docker version 24.0.6, build xxx

docker-compose --version
# 输出:Docker Compose version v2.21.0

看到版本号就说明装好了。

3. 推荐配置

  • 内存:至少4GB(Docker Desktop可以在设置里调)
  • 磁盘:至少20GB空闲空间(镜像会占用一些空间)
  • 网络:能访问Docker Hub(如果网速慢,可以配置国内镜像源)

快速部署方案一:使用成熟开源项目(推荐)

不想折腾?直接用开源项目,10分钟搞定。

1. 克隆项目

我推荐imeepo/dnmp,支持Arm CPU(Apple M系列芯片也能用):

git clone https://github.com/imeepo/dnmp.git
cd dnmp

2. 配置环境变量

复制示例配置文件:

cp .env.example .env

打开.env文件,改几个关键配置:

# MySQL root密码(别用123456,太弱了)
MYSQL_ROOT_PASSWORD=your_strong_password

# 时区设置
TZ=Asia/Shanghai

# 端口映射(如果80端口被占用,改成8080)
NGINX_HTTP_PORT=80
MYSQL_PORT=3306

3. 一键启动

docker-compose up -d

-d参数表示后台运行。第一次启动会拉取镜像,可能需要几分钟。看到这样的输出就成功了:

Creating network "dnmp_default" with the default driver
Creating dnmp_mysql_1 ... done
Creating dnmp_php_1   ... done
Creating dnmp_nginx_1 ... done
Creating dnmp_redis_1 ... done

4. 验证安装

打开浏览器,访问http://localhost,应该能看到phpinfo页面,显示PHP版本、已安装扩展等信息。

看到这个页面,就说明Nginx和PHP都跑起来了。

再访问http://localhost:8080,应该能看到PHPMyAdmin登录界面:

  • 服务器:mysql(注意不是localhost)
  • 用户名:root
  • 密码:你在.env里设置的密码

登录成功,说明MySQL也正常了。

5. 查看容器状态

docker-compose ps

输出应该类似这样:

Name               Command              State           Ports
--------------------------------------------------------------------
dnmp_nginx_1   nginx -g daemon off;   Up      0.0.0.0:80->80/tcp
dnmp_php_1     php-fpm                Up      9000/tcp
dnmp_mysql_1   docker-entrypoint...   Up      0.0.0.0:3306->3306/tcp
dnmp_redis_1   redis-server           Up      6379/tcp

State显示Up就说明容器在运行。

自定义方案二:手写docker-compose.yml(进阶)

想深入理解原理?自己写配置文件。

1. 创建项目目录

mkdir my-dnmp && cd my-dnmp
mkdir -p nginx/conf.d php mysql/data www

2. 编写docker-compose.yml

创建docker-compose.yml,内容如下:

version: '3.8'

services:
  # Nginx服务
  nginx:
    image: nginx:1.25-alpine  # 使用alpine版本,镜像更小
    container_name: dnmp-nginx
    ports:
      - "80:80"  # 映射80端口到宿主机
    volumes:
      - ./www:/var/www/html  # 代码目录
      - ./nginx/conf.d:/etc/nginx/conf.d  # 站点配置
      - ./nginx/logs:/var/log/nginx  # 日志目录
    depends_on:
      - php  # 依赖PHP服务,会先启动PHP再启动Nginx
    networks:
      - dnmp-network

  # PHP服务
  php:
    build: ./php  # 使用Dockerfile构建镜像
    container_name: dnmp-php
    volumes:
      - ./www:/var/www/html  # 代码目录(和Nginx保持一致)
    networks:
      - dnmp-network

  # MySQL服务
  mysql:
    image: mysql:8.0
    container_name: dnmp-mysql
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root123456  # root密码
      MYSQL_DATABASE: test_db  # 默认创建的数据库
      TZ: Asia/Shanghai  # 时区
    volumes:
      - ./mysql/data:/var/lib/mysql  # 数据持久化
    networks:
      - dnmp-network

  # Redis服务(可选)
  redis:
    image: redis:7-alpine
    container_name: dnmp-redis
    ports:
      - "6379:6379"
    networks:
      - dnmp-network

networks:
  dnmp-network:
    driver: bridge  # 桥接网络,容器间可以互相访问

3. 创建PHP Dockerfile

创建php/Dockerfile,安装常用扩展:

FROM php:8.1-fpm

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    libzip-dev \
    zip \
    unzip

# 安装PHP扩展
RUN docker-php-ext-install \
    mysqli \
    pdo_mysql \
    zip \
    opcache

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

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

4. 创建Nginx站点配置

创建nginx/conf.d/default.conf

server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php index.html;

    # 访问日志
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    # PHP文件转发给PHP-FPM处理
    location ~ \.php$ {
        fastcgi_pass php:9000;  # php是服务名,Docker自动解析IP
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # 静态文件直接返回
    location ~ \.(js|css|png|jpg|gif|ico)$ {
        expires 7d;
    }
}

5. 创建测试文件

创建www/index.php

<?php
phpinfo();

创建www/db_test.php测试数据库连接:

<?php
$host = 'mysql';  // 服务名,不是localhost
$user = 'root';
$pass = 'root123456';
$db = 'test_db';

try {
    $pdo = new PDO("mysql:host=$host;dbname=$db", $user, $pass);
    echo "数据库连接成功!<br>";
    echo "MySQL版本: " . $pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
} catch(PDOException $e) {
    echo "连接失败: " . $e->getMessage();
}

6. 启动服务

docker-compose up --build -d

--build参数表示重新构建PHP镜像。第一次启动会比较慢,因为要下载基础镜像和安装扩展。

验证和测试

1. 测试PHP

访问http://localhost,应该能看到phpinfo页面,检查一下:

  • PHP版本是8.1
  • mysqli、pdo_mysql、redis扩展都显示enabled

2. 测试数据库连接

访问http://localhost/db_test.php,应该看到”数据库连接成功”和MySQL版本号。

如果报连接失败,检查:

  • MySQL容器是否启动:docker-compose ps
  • host是否写成了localhost(应该是mysql)
  • 密码是否正确

3. 检查容器状态

docker-compose ps

所有容器的State都应该是Up

4. 查看日志排错

如果某个容器启动失败,查看日志:

docker-compose logs php  # 查看PHP容器日志
docker-compose logs -f nginx  # 实时查看Nginx日志(-f参数)

日志会告诉你具体哪里出错了,比如配置文件语法错误、端口被占用等。

团队协作最佳实践

版本控制策略

关键原则:配置上传,敏感信息不上传

创建.gitignore文件:

# 敏感信息不上传
.env

# 数据库文件不上传(太大)
mysql/data/

# 日志文件不上传
nginx/logs/*.log
php/logs/*.log

# 代码目录由业务项目管理,不在DNMP里
www/*
!www/.gitkeep  # 保留目录结构

提交到Git的文件

git add docker-compose.yml
git add .env.example  # 示例配置,不含真实密码
git add nginx/conf.d/
git add php/Dockerfile
git add php/php.ini
git add mysql/my.cnf
git commit -m "feat: add DNMP environment config"
git push

新成员入职流程(3分钟)

  1. 克隆项目:git clone xxx
  2. 复制配置:cp .env.example .env
  3. 修改密码:编辑.env,改MYSQL_ROOT_PASSWORD
  4. 启动环境:docker-compose up -d
  5. 导入数据:docker exec -i dnmp-mysql mysql -uroot -p < backup.sql

完事。不用问老员工”你Nginx配在哪?""你PHP装了啥扩展?”全都在配置文件里。

多PHP版本共存方案

场景:老项目跑PHP 7.4,新项目要PHP 8.1,怎么办?

方案一:在docker-compose.yml定义多个PHP服务

services:
  php74:
    image: php:7.4-fpm
    container_name: dnmp-php74
    volumes:
      - ./www:/var/www/html
    networks:
      - dnmp-network

  php81:
    image: php:8.1-fpm
    container_name: dnmp-php81
    volumes:
      - ./www:/var/www/html
    networks:
      - dnmp-network

Nginx配置不同项目指向不同PHP版本

nginx/conf.d/old-project.conf(老项目):

server {
    listen 80;
    server_name old.local;
    root /var/www/html/old-project;

    location ~ \.php$ {
        fastcgi_pass php74:9000;  # 指向PHP 7.4
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

nginx/conf.d/new-project.conf(新项目):

server {
    listen 80;
    server_name new.local;
    root /var/www/html/new-project;

    location ~ \.php$ {
        fastcgi_pass php81:9000;  # 指向PHP 8.1
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

配置hosts文件:

127.0.0.1 old.local
127.0.0.1 new.local

访问http://old.local走PHP 7.4,访问http://new.local走PHP 8.1。互不干扰。

数据持久化和备份

数据持久化已经通过卷挂载实现了,./mysql/data映射到容器的/var/lib/mysql,容器删了数据还在。

数据库备份脚本

创建scripts/backup.sh

#!/bin/bash
BACKUP_DIR="./backups"
DATE=$(date +%Y%m%d_%H%M%S)
MYSQL_CONTAINER="dnmp-mysql"
MYSQL_USER="root"
MYSQL_PASSWORD="root123456"
DATABASE="test_db"

mkdir -p $BACKUP_DIR

echo "开始备份数据库 $DATABASE..."
docker exec $MYSQL_CONTAINER mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD $DATABASE > $BACKUP_DIR/${DATABASE}_${DATE}.sql

echo "备份完成: $BACKUP_DIR/${DATABASE}_${DATE}.sql"

定期备份(使用cron)

# 编辑crontab
crontab -e

# 每天凌晨2点备份
0 2 * * * /path/to/scripts/backup.sh

恢复数据库

docker exec -i dnmp-mysql mysql -uroot -proot123456 test_db < ./backups/test_db_20251218.sql

常见问题和踩坑指南

问题1:端口被占用,容器启动失败

现象

Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use

排查

# Mac/Linux
lsof -i :80

# Windows
netstat -ano | findstr :80

解决方案

  • 方案1:停掉占用80端口的程序(比如Apache、IIS)
  • 方案2:改端口映射,在.env里改NGINX_HTTP_PORT=8080,访问localhost:8080

问题2:文件权限问题(Linux/Mac)

现象

  • Nginx报403 Forbidden
  • PHP无法写入文件,报Permission denied

原因
容器内www-data用户(UID 33)和宿主机用户(UID 1000)不一致,导致无法访问挂载的文件。

临时方案(开发环境)

chmod -R 777 ./www

正确方案(修改容器用户UID)

修改php/Dockerfile,让容器用户UID匹配宿主机:

FROM php:8.1-fpm

# 修改www-data用户的UID为宿主机用户的UID(比如1000)
RUN usermod -u 1000 www-data && groupmod -g 1000 www-data

# 其他配置...

重新构建镜像:

docker-compose build php
docker-compose up -d

问题3:PHP扩展缺失

现象

Fatal error: Call to undefined function mysqli_connect()

排查

docker exec dnmp-php php -m  # 查看已安装扩展

如果没看到mysqli,说明扩展没装。

解决方案

修改php/Dockerfile,添加扩展:

RUN docker-php-ext-install mysqli pdo_mysql

重新构建:

docker-compose build php
docker-compose restart php

问题4:数据库连接失败

现象

SQLSTATE[HY000] [2002] Connection refused

常见错误

  • 错误1:host写成了localhost127.0.0.1
  • 错误2:MySQL容器还没启动完成就连接了
  • 错误3:密码错了

正确姿势

$host = 'mysql';  // 必须用服务名,不是localhost
$user = 'root';
$pass = 'root123456';  // 检查是否和.env里一致

try {
    $pdo = new PDO("mysql:host=$host;dbname=test_db", $user, $pass);
    echo "连接成功";
} catch(PDOException $e) {
    echo "失败: " . $e->getMessage();
}

如果还是连不上,检查MySQL是否启动完成:

docker-compose logs mysql

看到mysqld: ready for connections就说明启动好了。

问题5:容器启动后立即退出

现象

docker-compose ps
# 某个容器Status显示Exit 1或Exit 127

排查命令

docker-compose logs 服务名

常见原因

  1. 配置文件语法错误

    • Nginx配置少了分号;
    • docker-compose.yml缩进不对(必须用空格,不能用Tab)
  2. 环境变量缺失

    • MySQL没设置MYSQL_ROOT_PASSWORD,启动失败
  3. 依赖服务未启动

    • Nginx依赖PHP,但PHP容器起不来

解决方法
根据日志提示修改配置文件,然后重新启动:

docker-compose down  # 删除所有容器
docker-compose up -d  # 重新创建并启动

结论

说了这么多,Docker Compose部署DNMP环境的核心价值其实就三点:

  1. 省时间:10分钟搞定,省下的时间可以多喝两杯咖啡
  2. 少踩坑:团队环境统一,再也不用半夜紧急排查”我电脑上能跑”的bug
  3. 易维护:配置文件就是文档,新人看一眼就懂,不用老员工手把手教

我之前手动装LNMP,经常搞到凌晨两点还跑不起来。自从用了Docker Compose,真的是”up一下,喝杯咖啡,环境就好了”。

别犹豫了,下班前花10分钟试一试。先clone个开源项目(imeepo/dnmp)跑起来,感受一下一键启动的爽感。等熟悉了,再自己写配置文件深入学习。

对了,如果遇到问题,别慌:

  1. 先看docker-compose logs查日志
  2. 搜一下GitHub Issues,大概率别人也遇到过
  3. 实在不行,到项目仓库提Issue,社区很活跃的

最后说一句,Docker Compose不只能部署PHP环境,Node.js、Python、Go都可以。学会了这套思路,以后换技术栈也不怕环境配置了。

下一步行动

  • 立即行动:git clone https://github.com/imeepo/dnmp.git,跑起第一个DNMP环境
  • 深入学习:看Docker Compose官方文档,学学更高级的用法(比如docker-compose.override.yml)
  • 分享传播:如果这篇文章帮到你了,分享给还在手动装环境的同事,让他们也解放一下

12 分钟阅读 · 发布于: 2025年12月18日 · 修改于: 2025年12月26日

评论

使用 GitHub 账号登录后即可评论

相关文章