Docker搭建LNMP环境:从容器化原理到PHP项目部署实战

发布时间:2026/8/22 3:35:54
Docker搭建LNMP环境:从容器化原理到PHP项目部署实战 1. 从零到一为什么选择Docker来搭建LNMP环境如果你是一个后端开发者或者正在学习Web开发那么“LNMP”这个词对你来说一定不陌生。它代表着Linux、Nginx、MySQL和PHP这套经典的Web服务栈是无数网站和应用赖以运行的基础。传统的搭建方式无论是用包管理器如yum、apt一个个安装配置还是用一键安装脚本都绕不开一个核心痛点环境依赖和版本冲突。你精心配置好的环境换一台服务器可能就因为某个库的版本差异而跑不起来想在同一台机器上测试PHP 7.4和PHP 8.2的项目更是需要繁琐的配置切换。这就是Docker登场的最佳时机。Docker通过容器化技术将应用及其所有依赖包括代码、运行时、系统工具、系统库打包成一个标准化的单元。对于LNMP环境来说这意味着你可以为Nginx、MySQL、PHP分别创建独立的容器它们彼此隔离互不干扰。你可以轻松地指定PHP的版本、MySQL的版本并且这个环境在任何安装了Docker的Linux机器上都能以完全相同的方式运行。今天我就以一个实际项目搭建者的身份带你走一遍用Docker从零搭建LNMP环境并部署一个PHP项目的完整流程。整个过程力求详细即便是Docker新手也能跟着一步步操作下来。2. 环境准备与Docker基础配置在开始构建我们的“乐高积木”之前得先把“工作台”和“积木块”准备好。这里的“工作台”就是你的Linux服务器或虚拟机“积木块”就是Docker引擎和相关的镜像。2.1 系统要求与Docker安装首先确保你有一台运行Linux的机器。主流的发行版如Ubuntu、CentOS、Debian都可以。我以Ubuntu 22.04 LTS为例进行演示其他系统的命令可能略有不同但思路一致。第一步卸载旧版本如果是全新安装可跳过为了避免冲突先清理系统中可能存在的旧版本Docker。sudo apt-get remove docker docker-engine docker.io containerd runc第二步安装必要的工具和添加Docker官方GPG密钥这些工具用于通过HTTPS使用仓库而GPG密钥用于验证软件包的完整性。sudo apt-get update sudo apt-get install ca-certificates curl gnupg lsb-release sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg第三步设置Docker的APT仓库将Docker的稳定版仓库添加到系统的软件源列表中。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引擎更新软件包索引并安装Docker CE社区版、CLI、Containerd等组件。sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin第五步验证安装并设置开机自启运行一个测试容器如果能看到“Hello from Docker!”的输出说明安装成功。同时将Docker服务设为开机启动并避免每次使用docker命令都要加sudo。sudo docker run hello-world sudo systemctl enable docker sudo systemctl start docker # 将当前用户加入docker组之后运行docker命令就不需要sudo了 sudo usermod -aG docker $USER注意执行usermod命令后你需要完全退出当前终端会话并重新登录或者重启系统用户组变更才会生效。否则你可能会遇到“Got permission denied while trying to connect to the Docker daemon socket”的错误。2.2 Docker核心概念与本次搭建的架构设计在动手编排容器之前花两分钟理解几个核心概念会让你后面的操作更加清晰镜像Image一个只读的模板包含了运行应用所需的文件系统、库和配置。比如nginx:latest、mysql:8.0都是镜像。你可以把它理解为一个应用程序的“安装包”或“蓝图”。容器Container镜像的运行实例。你可以创建、启动、停止、删除容器。每个容器都是相互隔离的。它就像是根据“蓝图”镜像建造并运行起来的一栋“房子”。数据卷VolumeDocker管理的数据持久化存储区域。容器内的数据是易失的容器删除数据就没了。数据卷可以将主机上的目录挂载到容器内实现数据的持久化和共享。对于MySQL数据库文件、网站代码、Nginx日志来说这是必需品。网络NetworkDocker允许你创建虚拟网络容器加入同一个网络后可以通过容器名相互通信就像在一个局域网内一样。这比使用容易变动的IP地址要可靠得多。基于这些概念我们本次LNMP环境的架构设计如下三个服务容器分别运行nginx、mysql、php-fpm。两个自定义网络可选但推荐一个网络供nginx和php-fpm通信因为它们需要紧密协作处理PHP请求。另一个网络供php-fpm和mysql通信用于数据库连接。这种划分在复杂项目中更清晰。三个数据卷mysql_data持久化MySQL数据库文件。web_root存放我们的PHP项目代码同时被Nginx和PHP-FPM容器挂载。nginx_conf存放自定义的Nginx配置文件。一个编排工具我们将使用docker-compose来定义和运行多容器的应用。通过一个docker-compose.yml文件就能描述整个应用的服务、网络、卷实现一键启动和停止。这个设计确保了环境的高可移植性、服务间的清晰隔离以及数据的持久安全。3. 使用Docker Compose编排LNMP服务docker-compose是管理多容器项目的利器。我们不再需要手动敲一堆docker run命令而是通过一个YAML文件声明所有服务。首先创建一个项目目录并编写我们的“总说明书”。3.1 创建项目结构与编写docker-compose.yml在你的家目录或任意你喜欢的位置创建一个项目文件夹并进入。mkdir ~/lnmp-docker-project cd ~/lnmp-docker-project接下来创建docker-compose.yml文件。这个文件是核心。vim docker-compose.yml将以下内容粘贴进去。我会逐段解释关键配置。version: 3.8 services: # MySQL 数据库服务 mysql: image: mysql:8.0 container_name: lnmp-mysql restart: always environment: MYSQL_ROOT_PASSWORD: your_strong_root_password_here # 务必修改为强密码 MYSQL_DATABASE: myapp_db # 可选容器启动时创建的默认数据库 MYSQL_USER: myapp_user # 可选创建的用户 MYSQL_PASSWORD: your_user_password_here # 对应用户的密码 volumes: - mysql_data:/var/lib/mysql - ./mysql/conf.d:/etc/mysql/conf.d:ro # 挂载自定义配置可选 networks: - backend command: --default-authentication-pluginmysql_native_password # 兼容旧版PHP驱动 # PHP-FPM 应用服务 php-fpm: image: php:8.2-fpm # 可根据需要选择版本如 php:7.4-fpm container_name: lnmp-php-fpm restart: always working_dir: /var/www/html volumes: - web_root:/var/www/html - ./php/php.ini:/usr/local/etc/php/conf.d/custom.ini:ro # 挂载自定义PHP配置 networks: - frontend - backend depends_on: - mysql # Nginx Web服务 nginx: image: nginx:alpine # alpine版本更小巧 container_name: lnmp-nginx restart: always ports: - 80:80 # 将宿主机的80端口映射到容器的80端口 - 443:443 # 如果需要HTTPS映射443端口 volumes: - web_root:/var/www/html:ro # 只读挂载代码目录 - ./nginx/conf.d:/etc/nginx/conf.d:ro # 挂载自定义Nginx站点配置 - ./nginx/logs:/var/log/nginx # 挂载日志目录方便查看 networks: - frontend depends_on: - php-fpm # 定义数据卷实现数据持久化 volumes: mysql_data: driver: local web_root: driver: local # 定义网络实现容器间通信 networks: frontend: driver: bridge backend: driver: bridge关键配置解析environment: 用于设置容器内的环境变量。对于MySQL这是设置root密码、创建数据库和用户最安全的方式。务必把your_strong_root_password_here替换成一个复杂的密码volumes: 挂载卷。mysql_data:/var/lib/mysql将名为mysql_data的卷挂到容器内的数据库存储路径这样即使容器删除数据也在。./nginx/conf.d:/etc/nginx/conf.d:ro将主机当前目录下的nginx/conf.d文件夹挂载到容器内Nginx读取配置的目录ro表示只读。这允许我们在主机上灵活修改配置。networks: 定义容器加入的网络。php-fpm同时加入了frontend与Nginx通信和backend与MySQL通信两个网络。depends_on: 定义启动顺序。php-fpm依赖mysql先启动nginx依赖php-fpm先启动。ports: 端口映射。“80:80”将宿主机的80端口暴露出来这样你就能通过http://服务器IP访问服务了。command: 覆盖容器默认的启动命令。对于MySQL 8添加--default-authentication-pluginmysql_native_password是为了确保与一些尚未更新到新认证方式的PHP MySQL扩展如老的mysqlnd兼容。这是一个非常实用的避坑点。3.2 准备配置文件与项目代码目录根据docker-compose.yml中的挂载路径我们需要在主机上创建对应的目录和文件。创建目录结构mkdir -p {nginx/conf.d,nginx/logs,php,mysql/conf.d}配置Nginx站点创建一个Nginx配置文件告诉它如何转发PHP请求。vim nginx/conf.d/default.conf输入以下内容server { listen 80; server_name localhost; # 或你的域名 root /var/www/html; index index.php index.html index.htm; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass php-fpm:9000; # 关键通过服务名“php-fpm”连接到PHP容器 fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.ht { deny all; } }核心要点fastcgi_pass php-fpm:9000;这一行是精髓。在Docker网络中容器之间可以通过在docker-compose.yml中定义的服务名这里是php-fpm直接进行通信无需知道其IP地址。这比使用link或指定IP要优雅和稳定得多。可选自定义PHP配置你可能需要调整PHP的上传文件大小、内存限制等。vim php/php.ini例如增加以下两行upload_max_filesize 100M post_max_size 100M memory_limit 256M准备网站根目录和测试文件# 由于我们定义了web_root卷Docker Compose启动时会自动创建对应的本地卷存储位置。 # 但为了方便我们可以先创建一个初始的index.php文件。 # Docker Compose启动后web_root卷的实际路径可以通过 docker volume inspect lnmp-docker-project_web_root 查看。 # 更简单的做法是在项目根目录创建一个 www 目录并修改docker-compose.yml将 ./www:/var/www/html。 # 这里为了演示卷的使用我们先启动服务再进入容器创建文件。 # 先启动服务见下一节然后执行 sudo docker exec -it lnmp-php-fpm bash # 进入容器后 echo ?php phpinfo(); ? /var/www/html/index.php exit当然更符合实践的做法是在主机上创建一个目录比如www将你的整个PHP项目代码放进去然后在docker-compose.yml中将web_root:/var/www/html替换为./www:/var/www/html。这样你就能在主机上直接编辑代码容器内实时生效。4. 启动服务、验证与日常管理所有配置就绪现在是见证成果的时刻。4.1 启动与查看服务状态在包含docker-compose.yml文件的目录下执行docker-compose up -d-d参数表示在后台运行detached mode。这条命令会按照依赖顺序拉取镜像如果本地没有、创建网络和卷并启动所有定义的服务。查看运行状态docker-compose ps你应该能看到三个服务的状态都是 “Up”。也可以使用docker ps查看。查看特定服务的日志对于排错非常有用docker-compose logs nginx # 查看Nginx日志 docker-compose logs php-fpm # 查看PHP-FPM日志 docker-compose logs mysql # 查看MySQL日志 docker-compose logs -f # 实时跟踪所有服务的日志CtrlC退出4.2 验证环境是否正常工作验证PHP信息打开浏览器访问你的服务器IP地址如果是本地虚拟机访问http://localhost或http://虚拟机IP。你应该能看到熟悉的phpinfo()页面。这证明Nginx成功接收请求并转发给了PHP-FPM处理PHP-FPM也成功执行了PHP代码。验证MySQL连接我们可以进入PHP容器使用命令行工具连接MySQL进行测试。docker exec -it lnmp-php-fpm bash进入容器后安装MySQL客户端因为官方的php:8.2-fpm镜像默认不包含mysql命令apt-get update apt-get install -y default-mysql-client然后连接MySQL使用docker-compose.yml中设置的环境变量mysql -h mysql -u root -p输入你设置的root密码。看到mysql提示符说明连接成功。输入SHOW DATABASES;应该能看到你创建的myapp_db数据库。输入exit退出MySQL再输入exit退出容器。实操心得在生产环境中更安全的做法是从PHP代码内部进行连接测试而不是在容器内安装客户端。这里只是为了演示网络连通性。PHP连接MySQL时主机名应填写服务名mysql这正是Docker网络提供的服务发现功能。4.3 日常管理与维护命令掌握了这些命令你就能轻松管理这个LNMP环境了停止所有服务docker-compose down这会停止并移除所有容器、网络默认的bridge网络不会移除但不会删除数据卷。你的MySQL数据和网站代码如果使用卷会保留。停止并移除所有资源包括数据卷docker-compose down -v警告这会删除docker-compose.yml中定义的所有卷数据库数据将丢失请谨慎使用。重启服务docker-compose restart [service_name] 如docker-compose restart nginx查看服务日志docker-compose logs [service_name]进入容器内部docker exec -it [container_name] bash 如docker exec -it lnmp-php-fpm bash构建/重新构建服务如果你有自定义的Dockerfile例如需要安装特定PHP扩展可以使用docker-compose build。更新镜像并重启docker-compose pull docker-compose up -d。这可以拉取服务镜像的最新版本并重新创建容器。4.4 部署一个真实的PHP项目以Laravel为例假设我们要部署一个Laravel项目。我们将采用更推荐的方式在主机上存放代码。修改挂载方式编辑docker-compose.yml找到web_root卷的挂载点将其从卷挂载改为目录挂载。# 在nginx和php-fpm服务的volumes部分将 - web_root:/var/www/html 替换为 - ./www:/var/www/html同时可以注释或删除volumes:部分对web_root:的定义因为我们不再需要那个命名卷了。准备项目代码cd ~/lnmp-docker-project # 假设你的Laravel项目在别处复制过来 cp -r /path/to/your/laravel-project ./www # 或者使用Git克隆 # git clone your-repo-url ./www调整目录权限Laravel需要storage和bootstrap/cache目录可写。由于容器内PHP进程通常以www-data用户运行而主机上的文件所有者可能是你的当前用户可能导致权限问题。有两种解决方法方法A简单适合开发在主机上赋予目录广泛写权限注意安全风险。chmod -R 775 ./www/storage ./www/bootstrap/cache方法B更规范在docker-compose.yml的php-fpm服务中添加用户映射让容器内的www-data用户使用与主机当前用户相同的UID。php-fpm: ... user: “1000:1000“ # 将1000替换为你主机用户的UID通过 id -u 和 id -g 查看 ...然后在主机上确保你的用户对./www目录有所有权。配置环境变量将Laravel的.env文件中的数据库配置指向我们的MySQL容器。DB_CONNECTIONmysql DB_HOSTmysql # 使用Docker服务名 DB_PORT3306 DB_DATABASEmyapp_db DB_USERNAMEmyapp_user DB_PASSWORDyour_user_password_here重启服务并执行安装docker-compose down docker-compose up -d进入PHP容器执行Laravel的安装命令docker exec -it lnmp-php-fpm bash cd /var/www/html composer install --no-dev --optimize-autoloader # 生产环境 php artisan key:generate php artisan migrate --seed # 如果需要迁移数据库 exit访问项目现在访问你的服务器IP应该就能看到部署好的Laravel应用了。5. 进阶配置、排错与优化建议一个基础环境跑起来只是开始要让其稳定、高效、安全地服务于生产或开发还需要考虑更多。5.1 为PHP-FPM安装必要的扩展官方的php:fpm镜像非常精简很多常用扩展如pdo_mysql,gd,zip,redis等需要手动安装。我们可以通过自定义Dockerfile来构建一个包含所需扩展的PHP镜像。在项目根目录创建php/Dockerfilevim php/Dockerfile写入以下内容# 使用官方PHP 8.2 FPM镜像作为基础 FROM php:8.2-fpm # 安装系统依赖和PHP扩展 RUN apt-get update apt-get install -y \ libpng-dev \ libjpeg-dev \ libfreetype6-dev \ libzip-dev \ zip \ unzip \ git \ curl \ docker-php-ext-configure gd --with-freetype --with-jpeg \ docker-php-ext-install -j$(nproc) gd pdo_mysql mysqli zip opcache \ apt-get clean rm -rf /var/lib/apt/lists/* # 可选安装Composer COPY --fromcomposer:latest /usr/bin/composer /usr/bin/composer # 设置工作目录 WORKDIR /var/www/html修改docker-compose.yml让php-fpm服务使用我们自定义的Dockerfile构建而不是直接使用官方镜像。php-fpm: build: ./php # 指定Dockerfile所在目录 # image: php:8.2-fpm # 注释掉这一行 container_name: lnmp-php-fpm ... # 其余配置保持不变重新构建并启动服务docker-compose build php-fpm # 构建新的PHP镜像 docker-compose up -d # 启动服务会使用新镜像创建容器5.2 常见问题排查思路“502 Bad Gateway” 错误检查PHP-FPM容器是否运行docker-compose ps。检查Nginx配置中的fastcgi_pass确保指向的服务名和端口php-fpm:9000正确。查看PHP-FPM日志docker-compose logs php-fpm看是否有进程启动错误或脚本执行超时。检查文件权限确保Nginx和PHP-FPM容器都能读取/var/www/html下的文件。如果使用目录挂载特别要注意主机文件的权限和所有者。无法连接MySQL数据库确认MySQL容器运行正常docker-compose logs mysql查看有无初始化错误。检查网络确保php-fpm服务加入了backend网络与mysql相同。验证连接参数在PHP代码或测试脚本中确认主机名是mysql端口是3306用户名密码与docker-compose.yml中environment设置的一致。MySQL 8认证插件问题如果你使用较老的PHP驱动如某些旧版的pdo_mysql可能会因为MySQL 8默认使用caching_sha2_password认证插件而连接失败。我们在docker-compose.yml中已经通过command参数强制使用了mysql_native_password插件来避免此问题。修改配置文件后不生效对于Nginx和PHP的配置我们是以“只读”:ro方式挂载的。修改主机上的配置文件后需要重启对应的容器才能生效docker-compose restart nginx或docker-compose restart php-fpm。对于docker-compose.yml本身的修改如环境变量、端口映射需要先停止服务再重新启动docker-compose down docker-compose up -d。5.3 性能与安全优化建议使用.env文件管理敏感信息将docker-compose.yml中的密码等敏感信息抽离到.env文件是更安全的做法。创建.env文件MYSQL_ROOT_PASSWORDyour_very_strong_password MYSQL_USER_PASSWORDanother_strong_password修改docker-compose.ymlenvironment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}确保.env文件不被提交到版本控制系统通过.gitignore忽略。调整资源限制在docker-compose.yml中可以为服务设置CPU和内存限制防止某个容器耗尽主机资源。php-fpm: ... deploy: resources: limits: cpus: ‘1‘ memory: 512M配置Nginx和PHP-FPM的进程数/连接数根据服务器硬件和业务负载调整nginx/conf.d/default.conf中的worker_processes、worker_connections以及PHP-FPM池配置可通过挂载www.conf文件覆盖。启用OPcache对于PHP生产环境务必启用并优化OPcache它能极大提升PHP脚本执行速度。这可以在我们自定义的php.ini中配置。日志轮转与监控将Nginx和PHP-FPM的日志挂载到主机后可以考虑使用logrotate工具配置日志轮转避免日志文件无限增大。同时可以结合docker stats命令或更专业的监控工具如PrometheusGrafana来监控容器资源使用情况。通过以上步骤你不仅得到了一个可运行的Docker化LNMP环境更掌握了一套可复制、可维护、易于扩展的现代化应用部署方法。从今天起你可以将这份docker-compose.yml和配置文件视为你的“环境即代码”资产在任何新服务器上一条docker-compose up -d命令就能重现完全一致的开发或生产环境。