Docker 部署个人网站全流程:Dockerfile 编写与 Compose 编排实战

一、为什么个人站长也要用 Docker

很多个人站长对 Docker 的第一印象是"大厂才用的东西",觉得自己的小站没必要搞容器化。其实恰恰相反,Docker 对个人站长非常友好:一台服务器上可以同时跑多个互不干扰的网站,每个网站的环境完全独立,不会出现"为了装一个扩展把系统搞崩"的惨剧;迁移服务器的时候,一条命令就能把整个环境打包带走,再也不用对着文档手工装一遍 Nginx、PHP、MySQL;踩坏了环境,直接删掉容器重建,几秒钟又是一条好汉。

我自己把两个站点迁到 Docker 之后,最大的感受就是省心。以前最怕的就是重装系统,各种环境变量、配置文件散落一地,迁一次站要折腾一整天;现在 docker compose up -d 一敲,环境整整齐齐。这篇文章就以经典的 Nginx 加 PHP-FPM 加 MySQL 架构为例,从 Dockerfile 编写到 Compose 编排,把完整流程走一遍。

二、先理解几个基础概念

在动手之前,先把三个最基础的概念弄清楚。镜像(Image)是一个只读的模板,里面装好了操作系统基础环境、软件和配置,可以理解成一张光盘;容器(Container)是镜像运行起来的实例,可以理解成用这张光盘启动的一台微型电脑,容器可以被创建、启动、停止、删除;数据卷(Volume)是容器外部的持久化存储,容器删除后数据不丢,专门用来存放数据库文件和上传的附件。

另外要记住一个关键点:容器是"一次性"的,容器内部的文件系统在容器删除后会一起消失,所以一切需要长期保存的数据都必须放在数据卷或挂载目录里。这个理念想通了,后面很多操作就顺理成章了。

三、编写 Dockerfile

Dockerfile 是构建镜像的配方文件,描述了一步步怎么把环境组装出来。以 PHP 站点为例,一个基础的 Dockerfile 长这样:

FROM php:8.2-fpm

RUN docker-php-ext-install pdo_mysql mysqli opcache \
    && docker-php-ext-enable opcache

WORKDIR /var/www/html
COPY . /var/www/html

RUN chown -R www-data:www-data /var/www/html

EXPOSE 9000
CMD ["php-fpm"]

FROM 指定基础镜像,官方镜像仓库里的 php:8.2-fpm 已经装好了 PHP-FPM;docker-php-ext-install 是官方镜像自带的扩展安装脚本,一行就能装上需要的 PHP 扩展;COPY 把当前目录的代码复制进镜像。注意最后一步把目录属主改成 www-data,否则 PHP 进程没有权限读写文件,网站会报 500 错误,这是新手最容易踩的坑。

四、多阶段构建与镜像瘦身

如果站点前端有构建步骤,比如用 npm 打包静态资源,可以用多阶段构建来减小镜像体积。多阶段构建的思路是:第一个阶段装 Node.js 完成打包,第二个阶段只把打包产物复制进来,最终镜像里完全没有 Node 和源码,体积能小一半以上。示例:

# 第一阶段:构建前端资源
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
RUN npm run build

# 第二阶段:运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80

另外几个瘦身的小技巧:优先使用 alpine 版本的基础镜像,体积通常只有标准版的一半;写 Dockerfile 时把变动少的指令放在前面,比如先 COPY 依赖清单再 RUN 安装依赖,这样代码变动时能充分利用构建缓存,重建速度快很多;不需要的包不要装进镜像,镜像越小,拉取和启动越快。

五、.dockerignore 必不可少

和 .gitignore 类似,.dockerignore 用来告诉 Docker 构建时忽略哪些文件,避免把缓存、日志、临时文件复制进镜像,既减小镜像体积,也避免把敏感信息带进去。一个常用的模板:

.git
node_modules
*.log
*.md
.DS_Store
runtime
backup

特别提醒:千万不要把 .env 这类包含数据库密码的配置文件漏掉,一旦被打进镜像又推送到公开仓库,等于把服务器密码公之于众。

六、用 docker-compose 编排整套环境

一个完整的网站环境通常需要 Nginx、PHP-FPM、MySQL 三个容器协同工作,用 docker-compose.yml 可以把它们一次性定义好、一键启动。示例配置如下:

version: "3.8"

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./www:/var/www/html
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - php
    restart: unless-stopped

  php:
    build: .
    volumes:
      - ./www:/var/www/html
    restart: unless-stopped

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 这里改成强密码
      MYSQL_DATABASE: mysite
    volumes:
      - mysql_data:/var/lib/mysql
    restart: unless-stopped

volumes:
  mysql_data:

几个要点解释一下。ports 把容器的 80 端口映射到宿主机的 80 端口;volumes 里的 ./www 是宿主机目录与容器目录的挂载,代码放在宿主机,改完代码刷新就能生效,不用重新构建镜像;mysql 的数据目录挂载到命名卷 mysql_data,这样即使容器删掉重建,数据库数据也完好无损;restart: unless-stopped 让容器在服务器重启后自动拉起,省去手工启动的麻烦。

七、构建、启动与日常操作

配置文件写好后,就可以开始构建和启动了。常用命令如下:

# 构建镜像并后台启动所有服务
docker compose up -d --build

# 查看服务状态
docker compose ps

# 查看日志(排错首选)
docker compose logs -f php

# 进入容器执行命令
docker compose exec php php -v
docker compose exec mysql mysql -u root -p

# 停止并删除容器(数据卷默认保留)
docker compose down

部署完成后,浏览器访问服务器 IP 或域名,看到站点正常打开,就说明整套环境跑起来了。日常更新代码时,把新代码放进挂载目录即可,或者重新 docker compose up -d --build 构建新镜像。数据库备份用 mysqldump 导出到宿主机挂载目录,再定期把备份文件下载到本地,双保险。

八、日志查看与资源监控

容器化之后,日志和监控的方式与传统部署略有不同,但思路是一样的:出了问题要能快速定位。查看某个服务的日志,用 docker compose logs 加服务名,-f 参数可以实时滚动输出,--tail 参数控制只看最近的行数:

docker compose logs --tail 100 -f php
docker compose logs --tail 200 nginx

查看容器资源占用用 docker stats,它会实时显示每个容器的 CPU、内存、网络和磁盘 IO 情况,排查"到底是哪个容器把机器拖垮了"这类问题非常直观:

docker stats

关于日志还有一个重要建议:把日志目录挂载出来。默认情况下容器日志由 Docker 统一管理,容器删除后日志也就没了,而且日志文件可能无限增长,最终撑满磁盘。可以在 compose 文件里给服务配置日志轮转,限制单个文件大小和保留份数:

logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

同时把 Nginx 的访问日志、MySQL 的慢查询日志都输出到宿主机挂载目录,既能在容器重建后长期保存,也方便定期分析慢 SQL、统计访问情况。这套组合拳打下来,站点的运行状态基本就尽在掌握了。

九、常见问题与解决办法

最后把个人站长用 Docker 最常见的几个问题列出来。第一,容器内时区不对,日志时间差八个小时,解决方法是设置环境变量 TZ=Asia/Shanghai,或者在 Dockerfile 里执行 ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime;第二,文件权限问题,宿主机上传的文件属主不是 www-data,PHP 无法写入,用 chown 把挂载目录属主改成 www-data 即可;第三,端口冲突,80 端口被宿主机其他程序占用,把映射端口改成 8080 之类的其他端口,或者在 Nginx 配置里用反向代理转发;第四,容器重启后数据丢失,检查数据是否都放在了数据卷或挂载目录里,容器内直接写入的数据是留不住的。

还有两个安全方面的建议。一是 MySQL 容器不要映射到宿主机公网端口,容器之间通过内部网络互相访问就够了,把数据库暴露到公网等于敞开大门;二是基础镜像要定期更新,docker compose pull 拉取新镜像并重建容器,及时修复已知漏洞。镜像长期不更新,等于带着老漏洞在公网裸奔。

十、总结

Docker 的学习曲线其实很平缓,先跑通一个最简单的站点,再逐步加上数据库、缓存、多阶段构建,用不了多久就能熟练掌握。对个人站长来说,Docker 带来的环境隔离、快速迁移、一键重建这三大好处,是传统手工部署完全比不了的。文章里给出的配置都是可以直接使用的,建议在测试环境先跑一遍,把每个命令的作用都搞清楚,再迁移到生产环境。希望这篇实战教程能帮你顺利跨过 Docker 这道门槛。

Last modification:August 6th, 2026 at 08:31 am

Leave a Comment