1. 项目概述:为什么选择Docker来搭建LNMP?
如果你是一个经常在Linux服务器上折腾的开发者或运维,肯定对LNMP(Linux, Nginx, MySQL, PHP)这套黄金组合不陌生。传统上,我们会在服务器上直接安装Nginx、编译PHP、配置MySQL,整个过程繁琐不说,一旦某个环节出错,或者需要切换PHP版本,那简直就是一场灾难。更别提不同项目依赖不同环境时,端口冲突、版本不兼容的问题有多让人头疼了。
我最初也是这么过来的,直到开始用Docker。简单来说,Docker把LNMP的每个组件(Nginx, MySQL, PHP-FPM)都打包成了独立的、轻量的“集装箱”(容器)。每个容器都拥有自己独立的运行环境,互不干扰。你想用PHP 7.4跑老项目,同时用PHP 8.2测试新特性?用Docker可以轻松实现,只需要运行两个不同的PHP容器即可。项目部署和迁移也变得极其简单,因为整个环境及其配置都被“容器化”了,在任何安装了Docker的Linux机器上都能一键还原。
所以,这篇内容就是带你从零开始,在Linux系统上使用Docker,一步步搭建一个干净、隔离、可复用的LNMP开发/测试环境,并最终部署一个PHP项目。整个过程我会把每一步的原理、背后的考量以及我踩过的坑都讲清楚,目标是让你看完就能自己动手搭起来,并且理解为什么要这么做。
2. 环境准备与Docker基础
在开始搭建之前,我们需要确保基础环境是就绪的。这里假设你使用的是一台干净的Linux服务器(如CentOS 7/8, Ubuntu 20.04/22.04),并且拥有root或sudo权限。
2.1 安装与配置Docker
Docker的安装其实很简单,但有几个关键点需要注意,这直接关系到后续使用的稳定性和效率。
首先,卸载旧版本(如果有的话)。这步很重要,避免残留文件导致冲突。
sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine对于Ubuntu/Debian系统,使用apt-get remove。
接下来是安装。我强烈建议使用Docker官方提供的仓库进行安装,而不是使用系统自带的旧版本软件包。官方仓库的版本更新,兼容性更好。
安装必要的工具包:
# CentOS/RHEL sudo yum install -y yum-utils # Ubuntu/Debian sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-release添加Docker官方GPG密钥和仓库:
# CentOS/RHEL sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # Ubuntu/Debian sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null安装Docker引擎:
# CentOS/RHEL sudo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin # Ubuntu/Debian sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin启动并设置开机自启:
sudo systemctl start docker sudo systemctl enable docker验证安装:运行
sudo docker run hello-world。如果能看到欢迎信息,说明Docker安装成功并可以正常运行。
注意:如果你在虚拟机或某些云主机上安装Docker后,运行
docker info时遇到类似WARNING: No swap limit support或内核参数警告,通常不影响基础使用,但生产环境建议根据文档调整。如果遇到“virtualization support not detected”这类错误,说明你的系统(特别是某些VPS或虚拟机)没有开启虚拟化支持(如Intel VT-x/AMD-V),或者Docker Desktop所需的后台服务未启动。对于Linux服务器,我们安装的是Docker Engine,不依赖桌面环境的虚拟化,所以这个问题较少见,但如果是在Windows WSL2或某些特殊虚拟化平台上,可能需要检查主机的BIOS设置或虚拟化平台配置。
2.2 配置Docker镜像加速器
直接从Docker官方Hub拉取镜像,在国内速度可能很慢。配置一个国内镜像加速器是必不可少的步骤。
这里以阿里云镜像加速器为例(你需要有一个阿里云账号):
登录阿里云容器镜像服务控制台。
在“镜像工具” -> “镜像加速器”中,你会看到针对不同系统的加速器地址,形如
https://xxxx.mirror.aliyuncs.com。在Linux服务器上,编辑Docker的守护进程配置文件:
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://xxxx.mirror.aliyuncs.com"] } EOF请将
https://xxxx.mirror.aliyuncs.com替换为你从阿里云控制台获取的实际地址。你也可以添加多个镜像源,例如同时使用中科大源["https://docker.mirrors.ustc.edu.cn"]。重新加载配置并重启Docker:
sudo systemctl daemon-reload sudo systemctl restart docker验证是否生效:
sudo docker info,在输出中查找Registry Mirrors部分,应该能看到你配置的镜像地址。
实操心得:镜像加速器不仅能加快拉取速度,还能提高稳定性。我曾经在没配置加速器的情况下拉取一个几百MB的镜像,中途超时失败了好几次,配置后基本是满速下载。这是搭建环境前必做的一步,能节省大量等待时间。
2.3 理解Docker核心概念:镜像与容器
在动手之前,花两分钟理解两个核心概念,后面操作会顺畅很多。
- 镜像(Image):一个只读的模板,类似于虚拟机的快照。它包含了运行某个软件所需的所有内容:代码、运行时、库、环境变量和配置文件。我们接下来要拉的
nginx:latest,mysql:8.0,php:8.2-fpm都是镜像。 - 容器(Container):镜像的运行实例。你可以把它理解为一个轻量级的、隔离的进程。当我们运行
docker run nginx时,Docker会基于nginx镜像创建一个可读写的容器层,然后启动它。一个镜像可以创建多个容器。
我们的目标,就是用这三个镜像,创建三个相互协作的容器,组成LNMP服务。
3. 核心组件部署:MySQL、PHP-FPM与Nginx
我们将采用“分而治之”的策略,先分别启动MySQL和PHP-FPM容器,最后配置Nginx容器将它们连接起来。这里我不会使用docker run后面跟一长串参数的方式,而是采用更清晰、更易维护的docker-compose方式。但为了让你理解底层发生了什么,我会先讲解单容器运行的命令和原理。
3.1 部署MySQL数据库容器
数据库是应用的核心,数据持久化是关键。我们不能让数据保存在容器内部,因为容器停止或删除后,里面的数据就没了。所以必须使用“数据卷”将容器内的数据目录挂载到宿主机上。
单容器启动命令解析:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=your_strong_password \ -e MYSQL_DATABASE=myapp \ -v /path/on/host/mysql_data:/var/lib/mysql \ mysql:8.0-d:后台运行容器。--name mysql8:给容器起个名字,方便后续管理。-p 3306:3306:端口映射。将宿主机的3306端口映射到容器的3306端口。这样你就能通过宿主机IP:3306访问MySQL了。-e MYSQL_ROOT_PASSWORD=...:设置环境变量,这里是MySQL root用户的密码。务必替换成一个强密码!-e MYSQL_DATABASE=myapp:容器启动时自动创建一个名为myapp的数据库,方便后续使用。-v /host/path:/container/path:数据卷挂载。将宿主机目录/path/on/host/mysql_data挂载到容器内的MySQL数据目录/var/lib/mysql。这样数据库文件就实际保存在宿主机上了。mysql:8.0:指定使用的镜像及其标签。不写标签默认是latest,但生产环境建议指定具体版本,如8.0。
运行后,可以用docker ps查看容器状态,用docker logs mysql8查看启动日志。确认运行正常后,可以尝试连接:
# 进入容器内的MySQL命令行 docker exec -it mysql8 mysql -uroot -p # 然后输入上面设置的密码注意事项:
- 密码安全:永远不要在命令中直接使用简单密码,更不要将带密码的命令写入脚本提交到代码库。在实际生产或团队协作中,应使用Docker Secrets或环境变量文件来管理敏感信息。
- 数据卷路径:确保宿主机上的挂载路径(如
/data/mysql)存在且Docker进程有读写权限(通常Docker daemon以root运行,没问题)。你可以先mkdir -p /data/mysql。 - 版本选择:MySQL 8.0默认使用了新的身份验证插件
caching_sha2_password,一些老的客户端或PHP版本可能不支持。如果遇到连接问题,可以在创建容器时通过环境变量-e MYSQL_DEFAULT_AUTHENTICATION_PLUGIN=mysql_native_password指定使用旧的插件,或者在容器内修改用户插件。
3.2 部署PHP-FPM容器
PHP-FPM(FastCGI Process Manager)是PHP的进程管理器,负责处理Nginx转发过来的PHP脚本请求。
单容器启动命令解析:
docker run -d \ --name php-fpm8 \ -p 9000:9000 \ -v /path/on/host/www:/var/www/html \ --link mysql8:mysql \ php:8.2-fpm-p 9000:9000:暴露PHP-FPM的FastCGI服务端口9000。这样Nginx容器才能与之通信。-v /host/path/www:/var/www/html:这是最关键的一步。我们将宿主机上的项目代码目录/path/on/host/www挂载到PHP容器的/var/www/html。这意味着你在宿主机上编辑代码,容器内立即生效。--link mysql8:mysql:这是旧的容器链接方式,它允许PHP容器通过主机名mysql访问MySQL容器。在更现代的Docker网络实践中,我们倾向于使用自定义网络,但--link在简单场景下依然直观。它会将MySQL容器的IP等信息以环境变量和主机名的方式注入到PHP容器中。php:8.2-fpm:使用官方PHP 8.2 FPM镜像。你也可以选择php:7.4-fpm等版本。
启动后,我们需要进入PHP容器安装一些必要的扩展,比如pdo_mysql,否则PHP无法连接MySQL。
# 进入PHP容器 docker exec -it php-fpm8 /bin/bash # 在容器内安装扩展(基于Debian的镜像) apt-get update && apt-get install -y libpng-dev libzip-dev && docker-php-ext-install pdo_mysql mysqli gd zip # 安装完成后退出 exit提示:官方PHP镜像提供了非常方便的
docker-php-ext-install脚本来安装核心扩展。对于更复杂的扩展(如redis,mongodb),可能需要使用pecl安装。
实操心得:挂载代码卷时,要特别注意宿主机和容器内的用户权限问题。PHP-FPM进程通常以www-data用户运行,而你在宿主机上可能是root或另一个普通用户。如果PHP容器没有权限读取或写入挂载目录的文件,会导致网站报“Permission denied”错误。一个简单的解决办法是,在宿主机上修改项目目录的权限为755,或者更精细地调整所有者和组。我通常会在宿主机上执行chmod -R 755 /path/on/host/www来避免初期权限麻烦。
3.3 部署Nginx容器并配置反向代理
Nginx作为Web服务器和反向代理,接收用户请求,如果是静态文件(如.jpg, .css)则直接返回;如果是PHP请求,则转发给PHP-FPM容器处理。
单容器启动命令解析:
docker run -d \ --name nginx \ -p 80:80 \ -p 443:443 \ -v /path/on/host/www:/usr/share/nginx/html \ -v /path/on/host/nginx.conf:/etc/nginx/nginx.conf:ro \ -v /path/on/host/conf.d:/etc/nginx/conf.d:ro \ --link php-fpm8:php-fpm \ nginx:latest-p 80:80 -p 443:443:映射HTTP和HTTPS端口。-v /host/path/www:/usr/share/nginx/html:同样挂载项目代码目录。Nginx和PHP容器必须挂载同一个宿主机目录到各自容器内的Web根目录,这样它们才能访问到相同的文件。-v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro:挂载自定义的Nginx主配置文件。:ro表示只读,防止容器意外修改宿主机文件。-v /host/path/conf.d:/etc/nginx/conf.d:ro:挂载自定义的站点配置文件目录。这是更常见的做法,每个项目一个独立的.conf文件放在这里。--link php-fpm8:php-fpm:链接到PHP-FPM容器,使其可以通过主机名php-fpm访问。
现在,我们需要在宿主机上创建Nginx的配置文件。首先创建目录结构:
mkdir -p /path/on/host/nginx/conf.d然后创建站点配置文件/path/on/host/nginx/conf.d/myapp.conf:
server { listen 80; server_name localhost; # 或你的域名 root /usr/share/nginx/html; index index.php index.html index.htm; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass php-fpm:9000; # 这里使用链接的容器名 fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.ht { deny all; } }这个配置做了几件事:
- 监听80端口。
- 设置Web根目录为
/usr/share/nginx/html(即我们挂载的目录)。 - 定义了PHP请求的处理规则:将所有以
.php结尾的请求,转发到php-fpm:9000(即我们之前启动的PHP-FPM容器)。 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;这一行至关重要,它告诉PHP-FPM要执行的文件路径在哪里。如果这行配置错误,你会看到File not found.的错误。
配置完成后,重启Nginx容器使其生效:
docker restart nginx4. 使用Docker Compose编排LNMP服务
虽然上面分步启动的方式能工作,但管理三个容器的启动参数、链接关系非常麻烦。Docker Compose正是为了解决这个问题而生,它允许我们用一个YAML文件(docker-compose.yml)来定义和运行多容器应用。
4.1 编写docker-compose.yml文件
在项目根目录(例如/path/on/host)下创建docker-compose.yml文件:
version: '3.8' services: nginx: image: nginx:latest container_name: lnmp_nginx ports: - "80:80" - "443:443" volumes: - ./www:/usr/share/nginx/html - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/logs:/var/log/nginx depends_on: - php networks: - lnmp-network php: image: php:8.2-fpm container_name: lnmp_php volumes: - ./www:/var/www/html - ./php/custom.ini:/usr/local/etc/php/conf.d/custom.ini:ro depends_on: - mysql networks: - lnmp-network # 使用命令在容器启动后安装扩展 command: > sh -c "docker-php-ext-install pdo_mysql mysqli gd zip && php-fpm" mysql: image: mysql:8.0 container_name: lnmp_mysql ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: your_strong_password_here MYSQL_DATABASE: myapp MYSQL_USER: myuser MYSQL_PASSWORD: myuser_password volumes: - ./mysql/data:/var/lib/mysql - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro networks: - lnmp-network networks: lnmp-network: driver: bridge文件解析与优势:
- 版本:指定Compose文件格式版本。
- 服务定义:定义了三个服务
nginx,php,mysql。 - 网络:创建了一个名为
lnmp-network的自定义桥接网络。所有服务加入此网络后,可以通过服务名(如mysql,php)直接相互访问,无需旧的--link参数,更清晰、更现代。在Nginx配置中,fastcgi_pass就可以写成php:9000。 - 数据卷:所有持久化数据(代码、数据库、日志、配置)都挂载到宿主机当前目录下的子目录中,结构清晰,易于备份。
- 依赖:使用
depends_on控制启动顺序,确保MySQL先于PHP启动,PHP先于Nginx启动(尽管网络联通后,服务启动有微小延迟,应用层可能需要重试逻辑)。 - PHP扩展安装:通过覆盖
command,在容器启动时自动安装所需的PHP扩展。这是一种方式,另一种更好的方式是基于php:8.2-fpm镜像构建一个包含扩展的自定义镜像(使用Dockerfile)。 - MySQL初始化:通过将SQL脚本挂载到
/docker-entrypoint-initdb.d/目录,容器首次启动时会自动执行该脚本,可用于初始化表结构等。
4.2 启动与管理LNMP堆栈
在包含docker-compose.yml的目录下,执行以下命令:
- 启动所有服务(后台模式):
docker-compose up -d-d代表 detached,后台运行。首次运行会拉取镜像,然后创建网络、卷,并启动容器。 - 查看运行状态:
docker-compose ps - 查看所有容器日志:
docker-compose logs - 查看特定服务日志:
docker-compose logs nginx - 停止所有服务:
docker-compose down这个命令会停止并删除容器、网络(默认创建的网络)。但不会删除数据卷,所以你的数据库文件和项目代码都还在。 - 停止并删除所有资源(包括数据卷):
docker-compose down -v警告:这会删除docker-compose.yml中定义的所有匿名卷,导致数据库数据丢失!请谨慎使用。 - 重启某个服务:
docker-compose restart nginx - 进入容器:
docker-compose exec php /bin/bash
使用Docker Compose后,整个LNMP环境的生命周期管理变得极其简单和统一,配置文件即代码,非常适合团队协作和版本管理。
5. 项目部署与验证
环境搭好了,我们来部署一个实际的PHP项目进行验证。
5.1 部署一个简单的PHP应用
在宿主机上的项目目录/path/on/host/www下,创建以下文件:
index.php(用于测试PHP环境和Nginx配置):
<?php phpinfo(); ?>db_test.php(用于测试PHP连接MySQL):
<?php $host = 'mysql'; // 使用docker-compose中的服务名 $dbname = 'myapp'; // 与docker-compose中MYSQL_DATABASE一致 $user = 'myuser'; // 与docker-compose中MYSQL_USER一致 $pass = 'myuser_password'; // 与docker-compose中MYSQL_PASSWORD一致 try { $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $user, $pass); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); echo "数据库连接成功!<br>"; // 尝试创建一个简单的表并插入数据 $pdo->exec("CREATE TABLE IF NOT EXISTS test (id INT AUTO_INCREMENT PRIMARY KEY, message VARCHAR(255))"); $stmt = $pdo->prepare("INSERT INTO test (message) VALUES (?)"); $stmt->execute([date('Y-m-d H:i:s') . ' - 来自Docker LNMP的问候']); echo "数据插入成功!<br>"; // 查询数据 $stmt = $pdo->query("SELECT * FROM test ORDER BY id DESC"); while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) { echo "ID: " . $row['id'] . " - Message: " . $row['message'] . "<br>"; } } catch (PDOException $e) { die("数据库连接失败: " . $e->getMessage()); } ?>5.2 验证服务
- 验证Nginx与PHP:打开浏览器,访问
http://你的服务器IP。你应该能看到完整的phpinfo()输出页面,这表明Nginx成功将PHP请求转发给了PHP-FPM容器处理。 - 验证PHP连接MySQL:访问
http://你的服务器IP/db_test.php。如果一切配置正确,你会看到“数据库连接成功!”和插入的数据记录。如果看到连接失败的错误,请按以下步骤排查:- 检查
docker-compose ps确保三个容器都在运行(Up状态)。 - 检查PHP容器内是否安装了
pdo_mysql扩展(docker-compose exec php php -m | grep pdo_mysql)。 - 检查
db_test.php中的数据库连接参数(主机名、数据库名、用户名、密码)是否与docker-compose.yml中的environment定义完全一致。注意:在Docker Compose网络中,主机名就是服务名mysql,而不是localhost。 - 进入MySQL容器,验证用户和数据库是否创建成功:
docker-compose exec mysql mysql -uroot -p,输入root密码后,执行SHOW DATABASES;和SELECT user, host FROM mysql.user;。
- 检查
5.3 部署真实项目(如WordPress)
对于像WordPress这样的真实项目,步骤也非常类似:
- 下载WordPress:在宿主机
www目录下,下载并解压WordPress最新版。 - 配置数据库:确保
docker-compose.yml中MySQL服务已创建了WordPress需要的数据库和用户(可以通过MYSQL_DATABASE,MYSQL_USER,MYSQL_PASSWORD环境变量自动创建,或通过初始化SQL脚本创建)。 - 调整文件权限:WordPress需要向
wp-content目录写入文件。你可能需要在宿主机上调整权限:chmod -R 755 /path/on/host/www和chown -R www-data:www-data /path/on/host/www/wp-content(但要注意宿主机用户与容器内用户的映射,更复杂的权限管理可能需要研究Docker的user命名空间映射)。 - 访问安装向导:浏览器访问你的服务器IP,就会进入WordPress著名的“五分钟安装”界面,根据提示填写数据库信息(数据库主机填
mysql,数据库名、用户名、密码填你在docker-compose.yml中设置的)即可完成安装。
6. 进阶配置与优化
基础环境跑通后,我们可以进行一些优化,让它更适用于生产或开发场景。
6.1 自定义PHP配置与扩展
官方PHP镜像的默认配置可能不符合我们的要求。我们可以通过挂载自定义的php.ini文件来修改配置。
- 在宿主机创建
php/custom.ini文件(对应docker-compose.yml中的挂载点):upload_max_filesize = 64M post_max_size = 64M memory_limit = 256M date.timezone = Asia/Shanghai - 在
docker-compose.yml的php服务中,我们已经挂载了这个文件:- ./php/custom.ini:/usr/local/etc/php/conf.d/custom.ini:ro。PHP会自动读取conf.d目录下的所有.ini文件。 - 对于更多扩展,建议创建
Dockerfile来构建自定义PHP镜像。在项目根目录创建Dockerfile.php:
然后修改FROM php:8.2-fpm RUN apt-get update && apt-get install -y \ libpng-dev \ libzip-dev \ libonig-dev \ && docker-php-ext-install \ pdo_mysql \ mysqli \ gd \ zip \ mbstring \ && pecl install redis && docker-php-ext-enable redisdocker-compose.yml,将php服务的image: php:8.2-fpm替换为build: context: . dockerfile: Dockerfile.php。这样每次启动都会基于你的Dockerfile构建镜像,确保环境一致性。
6.2 Nginx优化与多项目配置
对于多项目,我们可以在nginx/conf.d/目录下为每个项目创建独立的.conf文件。
例如,新增一个blog.conf:
server { listen 80; server_name blog.yourdomain.com; root /usr/share/nginx/html/blog; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }然后在www目录下创建blog文件夹,放入你的第二个项目代码。重启Nginx容器即可:docker-compose restart nginx。
6.3 数据备份与迁移
这是Docker化部署最大的优势之一。
- 备份:整个环境的状态由
docker-compose.yml和几个宿主机目录定义。备份就是备份这些文件:docker-compose.yml:环境定义。www/:项目代码。mysql/data/:数据库文件(确保MySQL容器已停止,或使用mysqldump进行热备份)。nginx/conf.d/和php/custom.ini:配置文件。
- 迁移:在新服务器上安装好Docker和Docker Compose,将上述备份的目录和文件拷贝过去,直接运行
docker-compose up -d,一个完全相同的LNMP环境就起来了。
6.4 使用环境变量文件管理敏感信息
将数据库密码等敏感信息直接写在docker-compose.yml中不安全。我们可以使用环境变量文件。
- 创建一个
.env文件在docker-compose.yml同级目录:MYSQL_ROOT_PASSWORD=SuperSecretRootPass123! MYSQL_DATABASE=myapp MYSQL_USER=myuser MYSQL_PASSWORD=MyUserSecretPass456! - 修改
docker-compose.yml中的mysql服务环境变量部分:environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD} - 确保
.env文件不被提交到版本控制系统(在.gitignore中添加.env)。
7. 常见问题与故障排查实录
在实际操作中,你几乎一定会遇到一些问题。这里记录了我遇到的一些典型问题及其解决方法。
7.1 容器启动失败或不断重启
- 现象:
docker-compose ps显示容器状态为Restarting或Exited。 - 排查:
- 查看日志:这是第一步,也是最重要的一步。
docker-compose logs [服务名]会输出详细的错误信息。常见原因有:- 端口冲突:
Bind for 0.0.0.0:80 failed: port is already allocated。说明宿主机80端口已被占用(可能是已有的Nginx或Apache)。修改docker-compose.yml中的端口映射,例如改为- "8080:80",或者停止占用端口的服务。 - 权限问题:
Permission denied挂载卷。检查宿主机上挂载的目录(如./mysql/data)是否存在,以及Docker进程是否有读写权限。可以尝试sudo chmod -R 777 /path/on/host(仅用于测试,生产环境需精细控制)。 - 镜像拉取失败:网络问题或镜像不存在。检查镜像名和标签是否正确,尝试
docker pull [镜像名]手动拉取。 - 配置错误:例如Nginx配置文件语法错误。可以通过
docker-compose exec nginx nginx -t来测试配置文件语法。
- 端口冲突:
- 查看日志:这是第一步,也是最重要的一步。
7.2 Nginx报错 “502 Bad Gateway” 或 “File not found.”
- 现象:访问PHP页面出现502错误,或者显示“File not found.”
- 排查:
- 检查PHP-FPM容器:确保
php服务正在运行 (docker-compose ps)。确保PHP-FPM监听在9000端口 (docker-compose exec php netstat -tlnp | grep 9000)。 - 检查Nginx配置:确认
fastcgi_pass指向正确。在Docker Compose网络中,应使用服务名php:9000。检查SCRIPT_FILENAME参数是否正确指向了文件真实路径。一个常见的坑是$document_root变量未设置或设置错误,确保root指令在server或location块中已正确配置。 - 检查文件路径和权限:进入Nginx容器 (
docker-compose exec nginx /bin/sh),查看/usr/share/nginx/html/index.php文件是否存在。进入PHP容器,查看/var/www/html/index.php是否存在。确认PHP容器内的用户(通常是www-data)有读取该文件的权限。
- 检查PHP-FPM容器:确保
7.3 PHP无法连接MySQL
- 现象:PHP页面显示 “SQLSTATE[HY000] [2002] Connection refused” 或 “SQLSTATE[HY000] [1045] Access denied”。
- 排查:
- 检查MySQL容器:确保
mysql服务正在运行,并且3306端口在容器内监听 (docker-compose exec mysql netstat -tlnp | grep 3306)。 - 检查连接参数:
- 主机名:在PHP代码或配置中,主机名必须是
mysql(Docker Compose服务名),而不是localhost或127.0.0.1。因为localhost在PHP容器内指向容器自己,而不是MySQL容器。 - 用户名、密码、数据库名:必须与
docker-compose.yml或.env文件中定义的环境变量完全一致,注意大小写。
- 主机名:在PHP代码或配置中,主机名必须是
- 检查MySQL用户权限:使用root用户进入MySQL,检查为应用创建的用户是否拥有从任意主机(
%)或特定网络访问的权限。在Docker网络中,PHP容器会使用一个独立的IP连接MySQL。CREATE USER 'myuser'@'%' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON myapp.* TO 'myuser'@'%'; FLUSH PRIVILEGES; - 检查防火墙:如果MySQL服务映射了宿主机的端口(
- "3306:3306"),并且你尝试从宿主机外部连接,请确保云服务器安全组或宿主机的防火墙(如firewalld,ufw)允许3306端口入站。
- 检查MySQL容器:确保
7.4 性能问题与资源限制
默认情况下,容器可以使用宿主机的所有资源。在生产环境,你可能需要限制容器的CPU和内存使用,防止单个容器耗尽资源影响其他服务。
可以在docker-compose.yml中为每个服务添加资源限制:
services: mysql: ... deploy: # 注意:在Compose v3中,resources放在deploy下,且通常用于swarm模式。单机模式更推荐使用以下方式: # 对于 docker-compose up,使用以下格式: # cpus: '0.5' # 不推荐,已弃用 # mem_limit: 512m # 不推荐,已弃用 # 推荐使用: ulimits: nproc: 65535 nofile: soft: 20000 hard: 40000 # 对于CPU和内存限制,在单机模式下,更标准的做法是在 `docker run` 时使用 `--cpus` 和 `--memory` 参数。 # 在docker-compose中,对于非Swarm模式,社区更倾向于使用 `cpu_shares` 和 `mem_limit`(尽管部分属性在v3中标记为遗留)。 cpu_shares: 512 # 相对权重,默认1024 mem_limit: 512m # 内存硬限制 mem_reservation: 256m # 内存软限制设置资源限制是一个需要根据实际负载进行测试和调整的过程。过小的限制会导致服务性能低下,过大的限制则失去了限制的意义。
从最初的单个容器手动启动,到使用Docker Compose一键编排,再到自定义配置和优化,这套基于Docker的LNMP环境搭建流程已经相当完整。它最大的价值在于提供了一种可重复、可移植、隔离的开发部署环境。一旦你熟悉了这套流程,无论是搭建本地开发环境,还是在新的服务器上部署生产应用,效率都会得到质的提升。记住,容器化不是银弹,它引入了新的复杂度(网络、存储、编排),但带来的环境一致性和运维便捷性,在大多数场景下绝对是利大于弊的。下次当你需要为不同项目配置不同PHP版本时,你会庆幸自己用了Docker。