Docker容器配置LNMP下WEB环境是目前推荐的方式,尽量避免那些LNMP一键包安装包之类,这些一键包之前或多或少出现过上下游产业链被黑被挂马的事故。建议大家尽量还是自己通过Docker安装自己的LNMP环境,避免无意中被挂马。
由于CentOS已经停止维护,飘易推荐使用 Rocky Linux 或 Alma Linux 来替换 Centos 系统,这2个新系统几乎完美兼容centos,你之前熟悉的yum等命令可以直接使用,学习迁移成本几乎为零。目前,Rocky社区的声量稍微比Alma高一些,但这2个系统随自己喜好任选即可。
另外,docker环境下也不建议部署LNMPA,直接部署 LNMP 架构接口。传统的LNMP架构容易出现502错误,但docker时代已经很少出现502了。
使用 Docker Compose 部署(内存一般需2G及以上)。
这两款系统完全兼容,安装 Docker 的步骤是一致的:
# 1. 安装 Docker 引擎
yum install -y yum-utils yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# 2. 启动并设置开机自启
systemctl start docker systemctl enable docker 或者二合一命令: systemctl enable --now docker
通过 systemctl status docker 查看结果
建议将所有网站数据统一管理:
mkdir -p /home/docker-web/npm # 存放 Nginx Proxy Manager 数据 mkdir -p /home/docker-web/mysql # 存放 MySQL 数据 mkdir -p /home/docker-web/wwwroot # 存放多个网站的代码 mkdir -p /home/docker-web/php-conf # 存放自定义php.ini-叠加方式 mkdir -p /home/docker-web/ftp # 存放FTP cd /home/docker-web
/home/docker-web 目录下编写 docker-compose.yml
创建文件 vi docker-compose.yml,填入以下内容:
services: # 1. 数据库容器 (MySQL 5.7) db: image: mysql:5.7 container_name: mysql_db restart: always environment: MYSQL_ROOT_PASSWORD: 密码 ports: - "3306:3306" volumes: - ./mysql:/var/lib/mysql # 2. PHP 容器 (PHP 7.4) 首次根据Dockerfile文件编译 php74: build: context: . dockerfile: Dockerfile container_name: php74_service restart: always volumes: - ./wwwroot:/www - ./php-conf/custom.ini:/usr/local/etc/php/conf.d/custom.ini # 叠加php.ini配置 # 3. 反向代理与 SSL 管理器 (NPM) 已经包含了 Nginx nginx-proxy: image: 'jc21/nginx-proxy-manager:latest' container_name: nginx_proxy restart: always ports: - '80:80' # HTTP 端口 - '443:443' # HTTPS 端口 - '60081:81' # 管理后台端口 volumes: - ./npm/data:/data - ./npm/letsencrypt:/etc/letsencrypt - ./wwwroot:/www # 这里必须和 PHP 容器挂载同样的宿主机目录 depends_on: - php74 - db # 4.ftp ftp: image: stilliard/pure-ftpd container_name: ftp_service restart: always ports: - "60021:21" # FTP 标准端口 - "30000-30019:30000-30019" # 被动模式端口范围 volumes: - ./wwwroot:/home/ftpusers # 映射网页根目录 - ./ftp/db:/etc/pure-ftpd/passwd # 存放 FTP 账号数据 environment: PUBLICHOST: "公网IP" FTP_USER_NAME: "fadmin" # 默认初始账号(可选) FTP_USER_PASS: "密码" # 默认初始密码(可选) FTP_USER_HOME: "/home/ftpusers" # 指定用户的 UID 和 GID FTP_USER_UID: 82 FTP_USER_GID: 82 ADDED_FLAGS: -E # 仅允许已验证用户(即禁用匿名) # 5.phpmyadmin管理 --- 这里不推荐开启这个容器!!自带apache+php环境,内存占用过大! #phpmyadmin: # image: phpmyadmin:latest # container_name: phpmyadmin_service # restart: always # environment: # - PMA_HOST=db # 默认连接到你的 MySQL 容器 # ports: # - "60082:80"
脚本里 YOUR_PASSWORD 改成你的密码,对应宿主机映射的端口号比如60081这些,随自己喜好,建议改成高位端口号,避免被特征扫描。
phpmyadmin 如果要使用,建议直接利用NPM 挂在一个目录上去,可以直接利用现有的nginx+php环境,避免phpmyadmin容器里重复的apache+php环境,太重,内存也会多占用100-200MB。参考:https://www.piaoyi.org/computer/Docker-Phpmyadmin-npm.html
PHP容器需要注意下,你安装的程序可能依赖一些特定的库。比如Laravel依赖:
BCMath PHP Extension Ctype PHP Extension JSON PHP Extension Mbstring PHP Extension OpenSSL PHP Extension PDO PHP Extension Tokenizer PHP Extension XML PHP Extension 还有常用的GD库
在 docker-compose.yml 同级目录下创建文件:Dockerfile,编写内容如下:
# 使用轻量的 Alpine 版本 FROM php:7.4-fpm-alpine # 1. 安装 GD 库所需的系统依赖(解压、字体、图片格式支持) RUN apk add --no-cache libpng-dev libjpeg-turbo-dev freetype-dev libwebp-dev libxpm-dev # 2. 配置 GD 库 RUN docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp # 3. 安装 Laravel 必须的扩展 # pdo_mysql: 数据库连接 # bcmath: 高精度计算 # gd: 图像处理 # (Ctype, JSON, Mbstring, OpenSSL, Tokenizer, XML 在 7.4 官方镜像中通常已内置) RUN docker-php-ext-install pdo_mysql mysqli bcmath gd # Redis 缓存 #RUN printf "\n" | pecl install redis && docker-php-ext-enable redis
请注意看注释。
启动docker服务:
docker compose up -d
如果php有编译扩展,首次需带编译参数:
docker compose up -d --build
注意:由于涉及编译,第一次执行 --build 会耗时 1-3 分钟,后续启动则是秒开。编译生成本地镜像后,后续不需要再带参数 build。
首次安装之后的提示类似:
✔ Image mysql:5.7 Pulled 49.0s ✔ Image jc21/nginx-proxy-manager:latest Pulled 56.5s ✔ Image stilliard/pure-ftpd Pulled 26.8s ✔ Image docker-web-php74 Built 57.1s ✔ Network docker-web_default Created 0.0s ✔ Container php74_service Started 2.7s ✔ Container ftp_service Started 3.2s ✔ Container mysql_db Started 2.7s ✔ Container nginx_proxy Started 1.0s
看到上面的信息,就代表成功了。
这是目前 Docker 社区流行的方案,它专门为一个目的而生:管理反向代理和 SSL。NPM 是一个带图形界面的 Docker 容器,它专门负责“反向代理”和“自动领证(Let's Encrypt)”。它能帮你自动处理 acme.sh 脚本、自动续签,且不影响 PHP 容器的纯净。
1. 登录管理后台
访问 http://服务器IP:60081。(注意,默认81,建议改成高位端口号)
首次登录会提醒设置管理员邮箱、密码,请务必记牢。
2. 开设第一个网站 (例如 www.site1.com)
点击 Hosts -> Proxy Hosts -> Add Proxy Host。
Details 选项卡:
Domain Names: 输入 www.site1.com Forward Hostname / IP: 输入 php74_service (直接使用 Compose 里的服务名) Forward Port: 输入 9000 勾选 Block Common Exploits。
说明:9000 是 PHP-FPM(FastCGI 进程管理器)的默认监听端口。镜像 php:7.4-fpm-alpine 在制作时,内部的配置文件(/usr/local/etc/php-fpm.d/www.conf)里就定义了:listen = 9000
Custom Locations 选项卡 :啥也不填。
SSL右侧小齿轮图标(Settings),填入以下 Nginx 配置:
# 第一种:默认标准php配置
fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /www/site1$fastcgi_script_name; # 注意这里的路径 fastcgi_param PHP_VALUE "open_basedir=/www/site1/:/tmp/"; # 限制 PHP 活动范围 include fastcgi_params;
# 第二种:laravel或lumen - 需root定义到public
# 显式定义 root
root /www/www.site1.com/public;
index index.html index.php;
# 强制让 Nginx 处理静态资源,不走 PHP
location ~* \.(jpg|jpeg|gif|png|css|js|ico|webp|tiff|gz|svg|svgz|zip|rar|7z|exe|mp3|mp4|ogg|ogv|woff|woff2|ttf)$ {
access_log off;
expires 30d;
try_files $uri =404;
}
# 处理入口
location / {
# 优雅的处理laravel,lumen路由规则
try_files $uri $uri/ /index.php?$query_string;
}
# 核心 PHP 解析:不要包裹在额外的 location / 里面
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass php74_service:9000;
fastcgi_index index.php;
# 核心:确保这两行存在
fastcgi_param SCRIPT_FILENAME /www/www.site1.com/public$fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
# 兼容 Lumen 路由,添加 PATH_INFO
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
# 建议加上这一行,增加隔离性
fastcgi_param PHP_VALUE "open_basedir=/www/www.site1.com/:/tmp/";
# 增加连接超时限制(防止接口执行耗时任务报错)
fastcgi_read_timeout 300;
}SSL 选项卡 (自动续签):
SSL Certificate: 选择 Request a new SSL Certificate。 勾选 Force SSL (强制跳转 HTTPS)。 勾选 I Agree to the Let's Encrypt Terms of Service。 点击 Save。
关于“自动续签”
您不需要进行任何操作。Nginx Proxy Manager (NPM) 内部集成了定时任务,它会每隔一段时间检查证书有效期。如果证书剩余时间不足 30 天,它会自动联系 Let's Encrypt 完成验证并替换新证书,整个过程对网站访问完全无感。
3. 开设第二个网站 (例如 www.site2.com)
重复上述步骤,只需在 Nginx 配置中,将 /www/site1 改为 /www/site2 即可。
注意:NPM创建一个网站后,并不会在自动创建对应的目录,需要自行创建目录,比如 /home/docker-web/wwwroot/site1
如何确认 PHP 容器(尤其是 Alpine 版本)内部运行的用户及其 UID/GID?
直接查看容器内进程(最快)通过 ps 命令查看当前正在运行的 php-fpm 进程归属于哪个用户:
docker exec -it php74_service ps aux
输出示例:
PID USER TIME COMMAND 1 root 0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf) 7 www-data 0:00 php-fpm: pool www 8 www-data 0:00 php-fpm: pool www
Master process 通常显示为 root。
Pool www(工作进程)显示的用户(如 www-data)就是实际处理网页请求的用户。
查询特定用户的 UID/GID(最准确)如果你在上面的输出中看到了 www-data,可以用 id 命令查出它的具体数字:
docker exec -it php74_service id www-data
预期的返回结果(如果是 PHP Alpine 镜像):
uid=82(www-data) gid=82(www-data) groups=82(www-data),82(www-data)
这里的uid、gid分别是镜像里的用户、组的id。
为什么这个 82 很重要?
在 Rocky/Alma Linux 宿主机上,当你执行 ls -l 查看 ./wwwroot 下的文件时:
如果显示数字 82:说明宿主机系统不认识这个用户,但 PHP 容器内部能够正常读写。
权限冲突:如果你用宿主机的 root 创建了文件,权限通常是 0:0。此时 PHP 容器(UID 82)尝试写入日志或上传图片时,会报 Permission Denied。
运维建议:
如果你发现 PHP 的 UID 确实是 82,那么在宿主机上操作网页文件后,记得执行以下命令来保持权限一致:
# 在宿主机的 /home/docker-web 目录下执行
chown -R 82:82 wwwroot/
宿主机不能执行:chown -R www-data:www-data wwwroot/ (因为Rocky/Alma Linux 默认没有 www-data 用户)
在 Docker 架构下为每个网站配置 FTP,最安全且主流的做法是使用 pure-ftpd 镜像。由于 FTP 协议比较特殊(涉及主动/被动模式及端口范围),在 Docker 中部署需要注意端口映射。为了保持低学习成本和高安全性,建议将 FTP 作为一个独立的容器加入到你的 docker-compose.yml 中。
为每个网站开设 FTP 账号
该镜像允许你通过简单的命令在容器内创建虚拟用户,并限制该用户只能访问特定的子目录(例如 site1)。
开设 Site1 的 FTP 账号:
执行以下命令 pure-pw:
docker exec -it ftp_service pure-pw useradd site1_user -f /etc/pure-ftpd/passwd/pureftpd.passwd -u 82 -g 82 -d /home/ftpusers/site1
site1_user: 你想设置的 FTP 账号名。
/home/ftpusers/site1: 该账号登录后看到的根目录(对应宿主机的 ./wwwroot/site1)。
-u 82 -g 82 是为了匹配 PHP-FPM (Alpine) 的权限。通常 pure-ftpd 默认使用 UID 1000,如果你的 PHP 容器(Alpine 版)使用 UID 82,你可以在创建 FTP 用户时指定 -u 82。
输入以上命令后,会提示你输入FTP用户的密码
生效配置 mkdb:
每次添加或修改用户后,必须运行以下命令更新数据库:
docker exec -it ftp_service pure-pw mkdb
或者明确指定路径(pure-pw mkdb [目标pdb] -f [源passwd]):
docker exec -it ftp_service pure-pw mkdb /etc/pure-ftpd/passwd/pureftpd.pdb -f /etc/pure-ftpd/passwd/pureftpd.passwd
其他:如何修改已经存在的ftp用户的uid、gid
# 假设你的用户名是 fadmin
docker exec -it ftp_service pure-pw usermod fadmin -u 82 -g 82 -f /etc/pure-ftpd/passwd/pureftpd.passwd
docker-compose.yml把密码文件映射到了 /etc/pure-ftpd/passwd,但命令默认在 /etc/pure-ftpd/ 目录下寻找。需要显式地指定密码文件的路径。
提示:不能直接映射到/etc/pure-ftpd/,因为这个目录下还有ftp的conf等其他配置文件。直接映射,到被宿主机覆盖,导致ftp崩溃。
# 必须执行 mkdb 才能生效(同样需要指定文件路径)
docker exec -it ftp_service pure-pw mkdb /etc/pure-ftpd/passwd/pureftpd.pdb -f /etc/pure-ftpd/passwd/pureftpd.passwd
#验证是否成功
docker exec -it ftp_service pure-pw show fadmin -f /etc/pure-ftpd/passwd/pureftpd.passwd
在输出结果中查找 UID 和 GID 这一行,确认是否已经变成了 82。
如何修改php.ini?可利用镜像自带的 conf.d 目录(最优雅)
PHP 镜像默认会读取 /usr/local/etc/php/conf.d/ 目录下的所有 .ini 文件。你不需要修改主文件,只需叠加你的配置。
在宿主机创建一个目录 php-conf/,并在其中新建 custom.ini:
; 必须设置为 0,防止 FPM 尝试猜测不存在的路径 cgi.fix_pathinfo=0 ; 开发或频繁变动环境下建议关闭 opcache 的文件路径缓存 ;opcache.enable=0 ; 确保文件上传和执行内存足够,防止 pma 处理大数据时崩溃 memory_limit=256M upload_max_filesize = 64M post_max_size = 64M date.timezone = Asia/Shanghai
在 docker-compose.yml 中挂载这个 custom.ini 文件(注意:不能挂载整个目录,否则你安装的一些扩展就会被覆盖禁用):
php74: volumes: - ./php-conf/custom.ini:/usr/local/etc/php/conf.d/custom.ini
这样做的好处是:主配置文件保持原汁原味,你的改动一目了然。
修改后重启docker:docker compose up -d
上面的这些容器涉及到的宿主机的端口号需要对外开放了。防火墙开启,以及云服务器(阿里云、腾讯云、亚马逊等)的防火墙也要开放的。
# 确保防火墙放行并永久保存
firewall-cmd --permanent --add-port=80/tcp firewall-cmd --permanent --add-port=443/tcp firewall-cmd --permanent --add-port=60081/tcp firewall-cmd --permanent --add-port=60082/tcp firewall-cmd --reload
由于 FTP 需要被动端口进行数据传输,你需要在 Rocky/Alma Linux 的防火墙中开放相关主动、被动2种端口:
firewall-cmd --permanent --add-port=60021/tcp firewall-cmd --permanent --add-port=30000-30019/tcp firewall-cmd --reload
这种方案下,即使 PHP 容器被挂马,它也无法直接修改宿主机的 crond 或系统文件。
建议定期运行 来更新官方镜像补丁:
docker compose pull && docker compose up -d
代码位置:您的网页文件放在 /home/docker-web/wwwroot/site1 下即可。
数据库连接:代码中连接数据库的地址不再是 localhost 或 127.0.0.1,而是直接写 db(即 Compose 中的服务名)。
进阶小技巧:
如果你只是修改了 php.ini 的配置文件,而没有修改 Dockerfile,你甚至不需要 up -d,直接重启对应的容器即可:
docker compose restart php74 【推荐:操作 Compose 定义的服务】 或者 docker restart php74_service
这比重新构建要快得多,且不会中断其他容器(如 MySQL)的运行。
重启所有服务:
docker compose restart
查看运行状态:
docker compose ps
实时查看日志(排查启动错误):
docker compose logs -f --tail 20
本文结束。