Docker 部署个人网站实战:PHP + Nginx 容器化环境的搭建与日常维护

很多个人站长的服务器环境还是"裸奔"式的:直接在系统里装 Nginx、装 PHP、装 MySQL,插件一多、配置一乱,哪天系统升级或者换服务器,环境重搭一遍少说两三个小时,中间还容易漏配置。Docker 解决的就是这个问题:把 PHP、Nginx、MySQL 各自打包成独立的容器,用一份配置文件描述整个环境,不管在哪台机器上,一条命令就能把整套环境跑起来。这篇文章以最典型的"Nginx + PHP-FPM + MySQL"组合为例,从零带你完成 Docker 环境的搭建,并把日常维护和常见坑一次讲清楚。

一、先建立三个基本概念

用 Docker 之前,先花两分钟理解三个词:镜像(Image)是打包好的环境模板,比如官方 php:8.2-fpm 就是带 PHP-FPM 的镜像;容器(Container)是镜像运行起来后的实例,你的网站代码就跑在容器里;卷(Volume)是容器外面的存储空间,用来保存数据库文件和代码文件——因为容器本身是"用完即弃"的,升级镜像、重建容器都会清空容器内部的数据,必须用卷把重要数据挂在容器外。理解这三者的关系,后面所有的操作就都顺了。

这里顺带回答一个个人站长常问的问题:Docker 和宝塔这类面板到底选哪个?面板的优点是图形化、上手快,缺点则是环境黑盒、迁移麻烦、出了问题难排查;Docker 的优点是环境透明可复现、迁移快、不污染系统,缺点是学习成本略高。笔者的建议是:如果你愿意花半天时间把本文的流程完整走一遍,长期来看 Docker 更省心,尤其是当你以后要在多台服务器之间迁移、或者想在一台机器上跑多个互不干扰的站点时,Docker 的优势会非常明显。两个工具也不冲突,不少人是面板装 Docker 插件来用的,但无论用哪种方式,理解容器化的原理永远有好处。

二、安装 Docker 与规划目录

Debian/Ubuntu 系统上安装 Docker 官方源版本:

curl -fsSL https://get.docker.com | sh
systemctl enable --now docker

装完验证一下 docker version 能正常输出。然后规划目录结构,这里用一个清晰的布局:

/srv/www/
├── docker-compose.yml    # 环境描述文件
├── nginx/                # Nginx 配置
│   └── conf.d/site.conf
├── app/                  # 网站代码(挂载给 PHP 和 Nginx)
└── mysql/                # MySQL 数据目录(挂载为卷)

三、编写 docker-compose.yml

docker-compose.yml 是整套环境的"图纸",下面的配置是个人网站的标准模板,直接抄下来改改就能用:

version: "3.8"
services:
  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./app:/var/www/html
      - ./nginx/conf.d:/etc/nginx/conf.d
    depends_on:
      - php
    restart: always
  php:
    image: php:8.2-fpm
    volumes:
      - ./app:/var/www/html
    restart: always
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 改成强密码
      MYSQL_DATABASE: myblog
    volumes:
      - ./mysql:/var/lib/mysql
    restart: always

三个服务各司其职:nginx 负责接收外部请求并把 PHP 文件转发给 php 容器;php 容器里跑着 PHP-FPM,用官方镜像省去了编译扩展的麻烦,需要什么扩展用 Dockerfile 加即可;mysql 容器负责数据库,MYSQL_ROOT_PASSWORD 首次启动时生效,之后改它不生效。restart: always 让容器在崩溃或服务器重启后自动拉起,这个配置对个人站长非常重要,能省掉很多半夜爬起来手动重启的麻烦。

四、Nginx 配置与首次启动

Nginx 的站点配置要指向上游的 php 容器。在容器网络里,直接用服务名作为主机名访问,注意 fastcgi_pass 写的是 php:9000:

server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.php index.html;
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }
    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

配置就绪后,在 /srv/www 目录下执行:

docker compose up -d

首次执行会拉取三个镜像,稍等片刻。启动后查看状态和日志:

docker compose ps
docker compose logs -f

访问 http://服务器IP 看到 PHP 页面,环境就跑通了。以后启动、停止整套环境只需要 docker compose start / stop,不用再一个个管服务。

五、定制 PHP 环境:用 Dockerfile 装扩展

用官方镜像起步最快,但官方 php:8.2-fpm 镜像默认只带最基础的扩展,很多个人网站需要的 mysqli、pdo_mysql 都不在里头。直接连 MySQL 会报 Call to undefined function mysqli_connect 或者 PDOException: could not find driver。解决办法是写一个简单的 Dockerfile 在官方镜像之上加扩展:

# php/Dockerfile
FROM php:8.2-fpm
RUN docker-php-ext-install pdo_mysql mysqli \
    && docker-php-ext-install opcache \
    && docker-php-ext-enable pdo_mysql mysqli opcache

然后把 compose 文件里 php 服务的 image: php:8.2-fpm 换成 build: ./php,下次 docker compose up -d 时 Docker 会先构建再启动。装其他扩展也是同样的套路,比如装 gd 图形库需要先装系统依赖:docker-php-ext-install gd。第一次写 Dockerfile 可能会遇到构建失败,别慌,看构建日志缺什么系统库就补什么,这是每个用 Docker 的人都要经历的一步。

六、日常维护:更新、备份与迁移

日常维护最常做的是三件事。第一,升级镜像版本(比如 PHP 出安全更新):

docker compose pull
docker compose up -d

因为代码和数据都在卷里,重建容器不会丢数据,这正是容器化最大的好处。第二,备份数据库,最稳妥的方式是用 mysqldump 导出 SQL 文件:

docker compose exec mysql mysqldump -uroot -p myblog > backup.sql

网站代码和 Nginx 配置本来就是宿主机上的目录,直接打包即可。第三,换服务器迁移:新机器装好 Docker 后,把 /srv/www 整个目录拷过去,docker compose up -d 一条命令环境就恢复了——这也是容器化对个人站长最有吸引力的一点,换服务器从"半天"变成"十分钟"。

日常维护还离不开一套顺手的基础命令,收藏这份速查表,用到的时候照着敲:

  • docker compose ps —— 查看各容器运行状态;
  • docker compose logs -f php —— 跟踪某个容器的日志(-f 是持续跟随);
  • docker compose exec php bash —— 进入 PHP 容器内部执行命令,排错必备;
  • docker compose restart php —— 单独重启某个容器,改完配置最快生效的方式;
  • docker compose down —— 停止并删除容器(数据和代码都在卷和宿主机目录里,不会丢);
  • docker system prune -f —— 清理悬空镜像和构建缓存,磁盘紧张时用,注意确认没在用。

另外很多个人网站有定时任务的需求,比如每天跑一次 sitemap 生成脚本。这里有个常见误区:容器里默认没有 cron 服务,就算装上了,容器重启也会丢。最省心的做法是让宿主机来调度,用宿主机的 crontab 定时执行 docker compose exec 进入容器跑命令:

# crontab -e 添加一行,每天凌晨 3 点生成 sitemap
0 3 * * * cd /srv/www && docker compose exec -T php php /var/www/html/cron/sitemap.php

注意加上 -T 参数,避免在非交互环境下报错。这个模式简单可靠,容器的"临时性"反而成了优点:定时任务每次都在干净的环境里执行,不会在容器里积累垃圾。

七、避坑指南:个人站长最常见的六个坑

坑一:文件权限错乱。PHP 容器里的 www-data 用户和宿主机的 uid 不一致,导致上传文件后 Nginx 没权限读。解决思路是让宿主机目录权限与容器用户对齐,比如把 app 目录属主改成与容器 www-data 相同的 uid。坑二:时区不对。容器默认 UTC 时区,数据库里存的时间会差八小时,在 compose 文件里给 php 和 mysql 都加上环境变量 TZ=Asia/Shanghai。坑三:改了代码不生效。浏览器缓存和 PHP OPcache 都会让你以为没生效,可以先重启 php 容器:docker compose restart php。坑四:MySQL 数据卷权限。mysql 容器要求数据目录属主是 mysql 用户(uid 999),直接把宿主机目录挂进去前先 chown 999:999,否则容器起不来。坑五:端口冲突。80 端口被宿主机上残留的 Nginx 占着,容器会启动失败,先 systemctl stop nginx 并禁用,让 Docker 独占端口。

坑六:容器日志无限增长。Docker 默认把容器日志以 json-file 形式写在 /var/lib/docker/containers 目录下,如果不限制大小,日志文件会一路膨胀,几个月不清理就能把磁盘占满,表现就是网站突然打不开、各种服务写不进文件。解决方法是在 /etc/docker/daemon.json 里配置日志轮转:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

改完重启 Docker 服务后,新建容器自动生效,每个容器日志最多保留三个 10M 的文件。对已经运行中的容器,用 docker compose down 再 up -d 重建即可套用新配置。磁盘空间告急时还可以用 docker system df 查看各类资源占用,做到心里有数。

八、安全提醒与总结

最后说几句安全上的话:容器不等于安全隔离,别有"反正跑在容器里"就放松警惕的想法。生产环境至少要做到:MySQL 端口不要映射到宿主机(删掉 mysql 的 ports 配置,只在容器网络内访问);SSH 和面板的强密码、fail2ban 该上还是要上;镜像尽量用官方或知名镜像,别随便拉不明来源的镜像。总的来说,Docker 对个人站长的价值可以用一句话概括:环境即代码,搬家不怕,崩了不怕,重来一条命令。花一个下午把这套流程跑通,之后省下来的时间远不止一个下午。

Last modification:August 19th, 2026 at 02:37 pm

Leave a Comment