一台服务器同时托管多个网站,是每个开发者进阶路上都会遇到的需求。传统方式需要在系统层面安装多个LNMP环境,配置复杂且难以隔离。使用Docker配合docker-compose,可以让每个站点拥有独立的运行环境,互不干扰,维护成本大大降低。本文详解如何用docker-compose编排LNMP多站点,包括Nginx多域名配置、数据持久化、以及实战中的常见坑。
为什么要用Docker跑LNMP多站点
直接装LNMP当然能用,但问题在于:
- 多个PHP版本难以共存(老项目用PHP 7.x,新项目用PHP 8.x)
- Nginx配置耦合在系统里,改坏一个影响全部
- MySQL版本升级可能造成兼容性问题
- 换服务器要重装整个环境,费时费力
用Docker做多站点隔离,每个「站点容器组」独立运行。升级PHP只影响对应容器,不影响其他站点。真正做到「删库跑路」也不慌——容器一删,环境干净。
整体架构设计
多站点LNMP的容器结构有两种主流思路:
方案一:共享MySQL,各自独立Nginx+PHP
所有站点共用一个MySQL容器,每个站点有自己独立的Nginx+PHP-FPM容器。这种方式适合业务数据需要集中管理的场景。
方案二:完全独立,每个站点一套LNMP
每个站点有完整的Nginx+PHP+MySQL组合,完全隔离,互不影响。适合站点之间需要强隔离的情况。
本文以方案一为主,因为更贴近实际使用场景——大多数情况下多个站点共享一个数据库实例是合理的选择。
整体目录结构如下:
~/lnmp-multi/
├── docker-compose.yml
├── nginx/
│ ├── conf.d/
│ │ ├── site1.conf
│ │ └── site2.conf
│ └── vhost/
│ ├── site1/
│ └── site2/
└── mysql/
└── data/
docker-compose.yml完整配置
version: "3.8"
services:
mysql:
image: mysql:8.0
container_name: lnmp-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: your_strong_password
MYSQL_DATABASE: app_db
volumes:
- ./mysql/data:/var/lib/mysql
networks:
- lnmp-net
command: --default-authentication-plugin=mysql_native_password
site1-nginx:
image: nginx:alpine
container_name: site1-nginx
restart: always
ports:
- "80:80"
volumes:
- ./nginx/vhost/site1:/usr/share/nginx/html
- ./nginx/conf.d/site1.conf:/etc/nginx/conf.d/site1.conf
depends_on:
- site1-php
networks:
- lnmp-net
site1-php:
image: php:8.2-fpm-alpine
container_name: site1-php
restart: always
volumes:
- ./nginx/vhost/site1:/var/www/html
networks:
- lnmp-net
depends_on:
- mysql
site2-nginx:
image: nginx:alpine
container_name: site2-nginx
restart: always
ports:
- "8080:80"
volumes:
- ./nginx/vhost/site2:/usr/share/nginx/html
- ./nginx/conf.d/site2.conf:/etc/nginx/conf.d/site2.conf
depends_on:
- site2-php
networks:
- lnmp-net
site2-php:
image: php:8.1-fpm-alpine
container_name: site2-php
restart: always
volumes:
- ./nginx/vhost/site2:/var/www/html
networks:
- lnmp-net
depends_on:
- mysql
networks:
lnmp-net:
driver: bridge
Nginx多域名配置详解
站点1配置(site1.conf)
server {
listen 80;
server_name www.site1.com site1.com;
root /usr/share/nginx/html;
index index.php index.html;
access_log /var/log/nginx/site1_access.log;
error_log /var/log/nginx/site1_error.log;
location / {
try_files `$uri `/index.php?`$query_string;
}
location ~ \.php$ {
fastcgi_pass site1-php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html/`$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}
站点2配置(site2.conf)
server {
listen 80;
server_name www.site2.com site2.com;
root /usr/share/nginx/html;
index index.php index.html;
access_log /var/log/nginx/site2_access.log;
error_log /var/log/nginx/site2_error.log;
location / {
try_files `$uri `/index.php?`$query_string;
}
location ~ \.php$ {
fastcgi_pass site2-php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html/`$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\. {
deny all;
}
}
两个配置的核心区别是 fastcgi_pass 指向不同的 PHP-FPM 容器:site1-php 和 site2-php。这是最容易出错的地方,指向错误会导致502错误。
启动与验证
# 创建目录
mkdir -p ~/lnmp-multi/nginx/{conf.d,vhost/{site1,site2}} ~/lnmp-multi/mysql/data
# 写入测试文件到site1
echo "<?php echo "Site 1 - PHP " . phpversion(); ?>" > ~/lnmp-multi/nginx/vhost/site1/index.php
# 写入测试文件到site2
echo "<?php echo "Site 2 - PHP " . phpversion(); ?>" > ~/lnmp-multi/nginx/vhost/site2/index.php
# 启动所有容器
cd ~/lnmp-multi && docker-compose up -d
# 查看运行状态
docker-compose ps
启动后访问对应端口,看到PHP版本信息输出即为成功。site1显示 PHP 8.2.x,site2显示 PHP 8.1.x,说明两个站点已完全隔离。
数据持久化的关键点
- MySQL数据目录:必须映射到宿主机本地目录(./mysql/data),否则容器删除后数据全部丢失
- 站点文件目录:同样要映射到宿主机(./nginx/vhost/site1),方便直接编辑代码
- Nginx配置文件:每次修改conf后reload Nginx容器即可生效:
docker-compose exec site1-nginx nginx -s reload - 不要用匿名卷:docker-compose中不要省略volumes的主机路径映射
常用维护命令
# 重启所有容器
docker-compose restart
# 查看日志(跟随输出)
docker-compose logs -f site1-php
# 进入PHP容器排查问题
docker-compose exec site1-php sh
# 进入MySQL容器
docker-compose exec mysql mysql -uroot -p
# 完整停机(保留数据)
docker-compose down
# 清理数据重新开始
docker-compose down -v
常见问题排查
502 Bad Gateway:最常见的问题是PHP-FPM容器名称拼写错误,或PHP容器还没完全启动就开始处理请求。加一段健康检查即可:
site1-php:
image: php:8.2-fpm-alpine
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 10s
timeout: 5s
retries: 3
MySQL连接被拒绝:PHP连接MySQL时host填写的是容器名mysql,不是localhost,也不是127.0.0.1。
文件权限问题:Docker容器以www-data用户运行,如果挂载的目录权限不对,会报403错误。先执行:chmod -R 755 ./nginx/vhost/
总结
Docker+docker-compose做LNMP多站点,本质是把每个站点的运行环境封装成容器,通过网络桥接实现互通。核心要点就三个:Nginx根据server_name区分不同域名、PHP-FPM通过容器名找到对应服务、数据通过volumes持久化到宿主机。
这套方案的可维护性远高于传统LNMP部署。一台2核4G的服务器,跑5-10个中小型站点绰绰有余。需要迁移服务器时,只需把整个目录scp过去,新机器装好Docker,一行命令全量恢复。
相关阅读:
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论