用 Docker 部署网站,最大的好处是环境干净、迁移无脑:整个技术栈打成一个 compose 文件,换机器就是拷贝过去 docker compose up -d,十分钟上线。
这篇用一个完整的 LNMP 实战(Nginx + PHP-FPM + MySQL + Redis),讲 Docker 部署网站的完整方案:compose 编排、数据持久化、备份、更新、生产注意事项。
需要云服务器可以看看财富云计算:一键开服面板,NAT 型无独立公网 IP,价格比独立 IP 机型省一半以上,主流游戏快速部署。
一、架构设计
互联网 → Nginx 容器(80/443) → PHP-FPM 容器(9000) → MySQL 容器(3306)
↘ Redis 容器(6379)四个容器,一个 compose 文件管全部。数据全部挂卷,容器随便删重建。
二、完整 docker-compose.yml
# /opt/website/docker-compose.yml
version: "3.8"
services:
nginx:
image: nginx:1.25-alpine
container_name: web-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro # 站点配置
- ./nginx/certs:/etc/nginx/certs:ro # 证书
- ./wwwroot:/var/www/html:ro # 网站代码(只读)
- ./logs/nginx:/var/log/nginx # 日志
depends_on:
- php
networks:
- webnet
php:
image: php:8.1-fpm-alpine
container_name: web-php
restart: unless-stopped
volumes:
- ./wwwroot:/var/www/html
# 按需装扩展: 在 Dockerfile 里装,这里直接改镜像也行
environment:
- TZ=Asia/Shanghai
networks:
- webnet
mysql:
image: mysql:8.0
container_name: web-mysql
restart: unless-stopped
ports:
- "127.0.0.1:3306:3306" # 只映射到本地!方便用客户端连,但不暴露公网
volumes:
- ./data/mysql:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=根密码
- MYSQL_DATABASE=wordpress
- MYSQL_USER=wp
- MYSQL_PASSWORD=应用密码
- TZ=Asia/Shanghai
command: --default-authentication-plugin=mysql_native_password
networks:
- webnet
redis:
image: redis:7-alpine
container_name: web-redis
restart: unless-stopped
ports:
- "127.0.0.1:6379:6379"
volumes:
- ./data/redis:/data
command: redis-server --requirepass Redis密码 --appendonly yes
networks:
- webnet
networks:
webnet:
driver: bridge目录结构
/opt/website/
├── docker-compose.yml
├── nginx/
│ ├── conf.d/
│ │ └── site.conf
│ └── certs/ # SSL 证书放这
├── wwwroot/ # 网站代码
├── logs/nginx/ # 日志
└── data/
├── mysql/ # 数据库文件(命根子)
└── redis/ # Redis 持久化Nginx 站点配置
# nginx/conf.d/site.conf
server {
listen 80;
server_name 你的域名;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name 你的域名;
root /var/www/html;
index index.php index.html;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
# PHP 转发到 php 容器(容器名就是主机名)
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~* \.(jpg|css|js|png|webp|woff2)$ {
expires 30d;
access_log off;
}
}关键点:fastcgi_pass php:9000 —— Docker 网络内直接用容器名通信,不用 IP。
三、启动与验证
cd /opt/website
# 启动
docker compose up -d
# 看状态
docker compose ps
# 看日志(排障全靠它)
docker compose logs -f nginx
docker compose logs -f php
# 放网站代码
# 把 WordPress/Typecho 文件放到 ./wwwroot/
# 验证
curl -I http://127.0.0.1访问域名,按程序向导安装。数据库配置:
- 主机:
mysql(不是 localhost,容器间用服务名) - 库名:
wordpress - 用户:
wp - 密码:compose 里设的
四、PHP 扩展问题
php:8.1-fpm-alpine 官方镜像很精简,很多扩展没有。WordPress/Typecho 需要:mysqli/pdo_mysql、gd、mbstring、curl、zip、intl、opcache。
方案 A:用 Dockerfile 自己构建(推荐,可控)
# php/Dockerfile
FROM php:8.1-fpm-alpine
# 装依赖 + PHP 扩展
RUN apk add --no-cache \
libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev \
freetype freetype-dev libwebp libwebp-dev \
icu-dev libzip-dev oniguruma-dev curl-dev
RUN docker-php-ext-configure gd --with-jpeg --with-webp --with-freetype \
&& docker-php-ext-install -j$(nproc) gd mbstring mysqli pdo_mysql \
curl zip intl bcmath opcache
# OPcache 生产参数
RUN { \
echo 'opcache.enable=1'; \
echo 'opcache.memory_consumption=128'; \
echo 'opcache.max_accelerated_files=10000'; \
echo 'opcache.revalidate_freq=2'; \
} > /usr/local/etc/php/conf.d/opcache.ini
# Redis 扩展
RUN apk add --no-cache $PHPIZE_DEPS \
&& pecl install redis \
&& docker-php-ext-enable rediscompose 里改成:
php:
build: ./php # 用 Dockerfile 构建
# image: php:8.1-fpm-alpine # 不用官方镜像了方案 B:用第三方全功能镜像(快但不可控):wordpress:php8.1-fpm 自带全部 WordPress 依赖。
五、数据持久化与备份
备份脚本
#!/bin/bash
# /opt/scripts/web-backup.sh
DATE=$(date +%Y%m%d)
WEB_DIR="/opt/website"
# 1. 数据库(从容器里导出,不是宿主机目录!)
docker exec web-mysql mysqldump -u root -p根密码 \
--single-transaction --all-databases | gzip > "$WEB_DIR/backups/mysql-$DATE.sql.gz"
# 2. 网站文件
tar -czf "$WEB_DIR/backups/wwwroot-$DATE.tar.gz" -C "$WEB_DIR" wwwroot
# 3. 配置(域名换了、证书换了都要能恢复)
tar -czf "$WEB_DIR/backups/config-$DATE.tar.gz" -C "$WEB_DIR" nginx docker-compose.yml
# 4. 清理 14 天前
find "$WEB_DIR/backups" -name "*.gz" -mtime +14 -deletemkdir -p /opt/website/backups
crontab -e
0 3 * * * /opt/scripts/web-backup.sh恢复
# 停服务
cd /opt/website && docker compose down
# 恢复文件
tar -xzf backups/wwwroot-20261014.tar.gz -C /opt/website/
# 起 MySQL,导入数据
docker compose up -d mysql
sleep 10
gunzip < backups/mysql-20261014.sql.gz | \
docker exec -i web-mysql mysql -u root -p根密码
# 全部起来
docker compose up -d六、更新:镜像与代码分开
# 更新容器(镜像拉新版,数据卷不动)
cd /opt/website
docker compose pull
docker compose up -d # 只会重建有更新的服务
# 更新网站代码(WP/Typecho)
tar -xzf 新版本.tar.gz -C wwwroot/ --overwrite
# 清理旧镜像(省磁盘)
docker image prune -a注意:docker compose up -d 重建容器时,没挂卷的目录会丢。所以代码、数据、日志、配置全部要挂卷(上面的 compose 已经这么做了)。
七、生产环境注意事项
1. 安全
# MySQL/Redis 端口只映射 127.0.0.1(上面已做)
# 验证: 外网扫不到 3306
nmap -Pn -p3306 你的IP # 应该 filtered
# Docker 会绕过 ufw!安全组也要配
# 见 Docker 安全加固2. 资源限制
services:
mysql:
# ...
deploy:
resources:
limits:
memory: 1G
php:
# ...
deploy:
resources:
limits:
memory: 512M3. 日志管理
Docker 容器日志默认无限增长,能撑爆磁盘:
# 每个服务加:
logging:
driver: "json-file"
options:
max-size: "100m"
max-file: "3"或者全局配置 /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {"max-size": "100m", "max-file": "3"}
}4. 健康检查
nginx:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3配合 depends_on: condition: service_healthy,避免 PHP 没起来 Nginx 先起报 502。
八、常见问题
Q:容器内连不上 mysql?
连接串主机名写 mysql 不是 localhost;确认两个容器在同一 network(compose 默认同网络);等 MySQL 初始化完(第一次启动要 10–30 秒)。
Q:网站代码改了没生效?
OPcache 缓存了 PHP。docker compose restart php,或者调小 opcache.revalidate_freq。
Q:磁盘越用越少?docker system df 看占用,docker system prune -a 清理。日志、旧镜像是大头。
Q:HTTPS 证书怎么自动续期?
宿主机跑 certbot,或者用 nginx-proxy-manager(自带证书管理和可视化反代,省事)。
Q:和宿主机直接装 LNMP 比,性能差多少?
网络多一层 NAT 转发,约 1–3% 损耗;磁盘 IO 基本持平。换来的是环境隔离和迁移便利,值。
Q:NAT 型服务器用 Docker 建站?
端口链条:公网 → 网关映射 80/443 → 宿主机 → 容器。三层确认:服务商映射了 80/443、安全组放行、compose ports 映射对。域名备案后正常访问。
小结
Docker 部署网站的核心就四点:compose 编排一把梭、数据全部挂卷、端口只暴露必要的、日志设上限。搭好之后,加服务就是加一段 YAML,换机器就是拷贝目录,维护成本比裸装低一个量级。
更多 Docker 与网站内容见财富云计算知识库:Dockerfile 优化、Compose 编排、安全加固全系列;网站搭建:Nginx、WordPress、HTTPS、CDN。需要云服务器可以看看财富云计算:一键开服面板,NAT 型无独立公网 IP,价格比独立 IP 机型省一半以上。
延伸阅读
- 网站环境怎么选:LNMP 手动 / 宝塔面板 / Docker / 云开发
- 问卷表单系统搭建:SurveyKing / Formbricks,数据自己收
- 图床搭建:Lsky Pro / Lychee,自托管图片托管方案
- 邮件列表搭建:listmonk 自托管 Newsletter,把读者握在自己手里
- 文档站点搭建:MkDocs / VuePress / Docsify,产品文档与技术手册方案
- RSS 阅读器搭建:FreshRSS 自托管,把信息流拿回自己手里
- 短链接服务搭建:Shlink 自托管,链接数据自己掌握
- 网站状态监控页搭建:Uptime Kuma,服务器挂了第一时间知道

[...]docker compose up -d铁律:生产环境跑带版本号的镜像,永远不要跑 latest。回滚就是 docker compose up -d 前把镜像标签改回旧版本号,30 秒搞定。配合 CI 更香:GitHub Actions / Gitee Go 里跑 docker build + Trivy 扫描 + 推送,推代码自动出镜像,详细玩法见Docker 部署网站实战。九、常见问题速查现象[...]
2026-10-14 回复
[...]docker exec myapp cat /var/log/nginx/access.log > /tmp/evidence.log处置顺序:先断网止血 → 导出日志证据 → 分析入口 → 重建容器 → 修补配置。不要一慌了事直接删容器,日志证据没了就查不到入侵路径。更完整的响应流程见数据泄露应急响应。八、一页清单(打印贴墙)[ ] 所有容器 --user 非 root 运行[ ] --c[...]
2026-10-14 回复
[...]docker system df -v # 明细到每个镜像/容器/卷Dockerfile 编写规范见Dockerfile 编写与镜像优化,网站部署全流程见Docker 部署网站实战,Compose 编排基础见Docker Compose 编排实战。数据卷和网络是 Docker 的两条命脉:卷决定数据活多久,网络决定容器怎么说话。记住三件事——要留的数据进卷、容器互访用服务名、U[...]
2026-10-15 回复
[...]docker exec registry bin/registry garbage-collect /etc/docker/registry/config.yml磁盘涨得最快的三样:CI 每次构建的新层、没人清理的旧标签、latest 反复覆盖的层。每周一次 GC + 保留策略(只留最近 10 个版本),仓库能长期健康。七、常见问题现象原因解法push 报 http: server gave HT[...]
2026-10-15 回复
[...]netsh advfirewall firewall add rule name="GameRange" dir=in action=allow protocol=UDP localport=26901-26903完整的 Windows 环境准备(远程桌面连接、运行库安装、SteamCMD 用法、开机自启、8 个常见坑)见:《Windows 云服务器开服通用前置教程》→ htt[...]
2026-10-18 回复
[...]restart: unless-stoppedGhost(Docker + Nginx 反代):官方镜像 + 443 反代,配置方法同Docker 部署网站实战的套路。四、博客的通用功课不管选哪个平台,这些事都逃不掉:备份——写了三年的博客丢过一次就懂了。数据库 + 附件每日自动备份,异地一份,方案见服务器备份与快照策略。HTTPS——现在没有 SSL 的博客,浏览器直接标"不安全",读者信任打对[...]
2026-10-18 回复