1. 前言
为自己的个人博客部署一个评论服务是必要的。由于我的博客是部署在 Vercel 的静态网站,没有后端服务,所以我选择了 Waline 作为评论服务,最初用的是 Vercel + Neon(PostgreSQL)的白嫖方案。但最近 Waline 更新之后,出现了两次无法服务的问题,最后我咬咬牙买了一台云服务器,想通过 Docker 部署私有化的 Waline 评论服务:一方面避免这种报错导致评论功能整体不可用,另一方面也是觉得白嫖的免费数据库始终有"跑路"风险,想把数据攥在自己手里。

2. 准备工作
-
购买了一台腾讯云的轻量云服务器,2核4G配置,系统为Ubuntu 24.04.4 LTS x86_64。为了避免 ICP 备案,以及 Docker 镜像在国内网络环境下拉取不稳定的问题,这里选择了腾讯云的海外机房。
-
给 Waline 服务分配了一个独立子域名(本文以
waline.dearom.com为例),这样博客主站和评论服务互不影响,将来迁移或更换评论系统也更方便。 -
服务器上需要提前安装好Docker和Docker Compose插件,这一步比较通用,网上教程很多,本文不再展开。
-
为了减少服务器被攻击的面,这里采用Nginx反代的方式,只允许通过443端口访问Waline服务,PostgreSQL端口不对公网开放。整体架构大致是:

3. 为 PostgreSQL 和 Waline 容器化
3.1 创建 Docker Compose 文件
在服务器上新建目录(例如 ~/waline),并在其中创建 docker-compose.yml:
services:
postgres:
image: postgres:16-alpine
container_name: waline-postgres
restart: unless-stopped
environment:
POSTGRES_DB: waline
POSTGRES_USER: waline
POSTGRES_PASSWORD: ${PG_PASSWORD}
volumes:
- /srv/waline/db:/var/lib/postgresql/data
networks:
- waline-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U waline -d waline"]
interval: 10s
timeout: 5s
retries: 5
waline:
image: lizheming/waline:latest
container_name: waline
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "127.0.0.1:8360:8360"
volumes:
- /srv/waline/data:/app/data
environment:
TZ: "Asia/Shanghai"
SITE_NAME: "你自己的网站名"
SITE_URL: "你自己的网址"
AUTHOR_EMAIL: "你自己的邮箱地址"
PG_HOST: postgres
PG_PORT: 5432
PG_DB: waline
PG_USER: waline
PG_PASSWORD: ${PG_PASSWORD}
PG_PREFIX: "wl_"
SECURE_DOMAINS: "你自己的域名"
networks:
- waline-network
networks:
waline-network:
driver: bridge注意 ports 这里特意写成 127.0.0.1:8360:8360,而不是 8360:8360——这样Waline只监听本机回环地址,公网无法直接访问8360端口,只能通过后面配置的Nginx反代进入,这也是前面提到的"减少攻击面"的关键一环。
3.2 创建数据目录
sudo mkdir -p /srv/waline/db
sudo mkdir -p /srv/waline/data3.3 创建 .env 文件
.env 文件需要和 docker-compose.yml 放在同一个目录下(也就是本文示例中的 ~/waline 目录),Docker Compose 默认只会读取同级目录的 .env,放错地方会导致密码变量读取不到。
PG_PASSWORD=你的强密码3.4 启动服务
准备工作做完之后,就可以启动容器了:
# 校验 compose 文件语法是否正确
docker compose config
# 拉取镜像
docker compose pull
# 后台启动所有服务
docker compose up -d
# 查看容器运行状态
docker ps
# 查看 Waline 容器最近 100 行日志,确认是否正常连接数据库
docker compose logs --tail=100 waline
# 查看 PostgreSQL 容器最近 50 行日志
docker compose logs --tail=50 postgres通过上述过程确认容器都是 Up 状态,且日志里没有报错,说明这一步就完成了。
随后还需要参考官网的多数据库服务支持,主要是需要给PostgreSQL导入 waline.pgsql初始化数据库。
4. Nginx 反向代理
4.1 安装 Nginx
sudo apt update
sudo apt install -y nginx
nginx -v
sudo systemctl status nginx --no-pager4.2 先用80端口跑通反代
不建议一上来就配 443,先用最简单的80端口配置把反代链路跑通,确认Waline容器能被正常访问,再申请证书。这样即使中间出问题,也更容易定位是反代配置的问题还是证书的问题。
新建配置文件(例如 /etc/nginx/sites-available/waline.conf):
server {
listen 80;
server_name waline.dearom.com;
location / {
proxy_pass http://127.0.0.1:8360;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Waline 依赖 WebSocket 做实时通知,这里需要显式开启升级支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}启用配置并让 Nginx 重新加载:
sudo ln -s /etc/nginx/sites-available/waline.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginxnginx -t 是校验配置文件语法是否正确,没有报错再执行 reload,避免因为写错配置导致 Nginx 直接挂掉。
配置好之后,用 http://waline.dearom.com 直接访问,确认能拿到 Waline 的响应(说明域名解析、Nginx 反代、Waline 容器这条链路都是通的),再进行下一步。
4.3 确认可用后再申请SSL证书
80端口验证没问题后,再用Certbot申请Let's Encrypt证书(申请前需要确保域名已经解析到服务器IP,且80端口能被外网访问到,因为Certbot默认用 HTTP-01 方式做域名验证):
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d waline.dearom.com按提示输入邮箱、同意条款即可。Certbot 的 Nginx 插件会自动检测到上面的 80 端口配置,帮你把证书写入 /etc/letsencrypt/live/waline.dearom.com/ 目录,并自动改写配置文件,加上 443 端口的 server 块,同时把 80 端口改成跳转到 443(也可以在申请时按提示选择是否强制跳转)。
最后再跑一次 sudo nginx -t && sudo systemctl reload nginx,确认 443 端口也能正常访问即可。
5. 接入Cloudflare Turnstile人机验证
私有化部署之后,评论区直接暴露在公网,光靠Nginx只放行 443 端口还不够,机器人刷评论、脚本爆破的问题还是得靠人机验证来挡。Waline内置了对Cloudflare Turnstile的支持,配置起来也不复杂,这里顺手记一下。
5.1 在 Cloudflare 申请 Site Key / Secret Key
- 登录 Cloudflare Dashboard,左侧菜单找到 Turnstile。
- 点击Add site,填写:
- Site name:随便起一个能识别的名字,比如
dearom-waline - Domain:这里要同时加上博客主域名和 Waline 服务端域名(不带
http:///https://,比如dearom.com和waline.dearom.com),否则跨域校验会失败 - Widget Mode:一般选Managed(托管模式)即可,兼顾体验和安全
- Site name:随便起一个能识别的名字,比如
- 创建完成后会拿到一对 Key:
- Site Key(前端用,对应 Waline 的
TURNSTILE_KEY) - Secret Key(服务端校验用,对应 Waline 的
TURNSTILE_SECRET)
- Site Key(前端用,对应 Waline 的
5.2 把 Key 写进 docker-compose
在waline服务的environment里新增两行,跟前面的 PG_* 变量放在一起:
waline:
# ...
environment:
TZ: "Asia/Shanghai"
SITE_NAME: "你自己的网站名"
SITE_URL: "你自己的网址"
AUTHOR_EMAIL: "你自己的邮箱地址"
PG_HOST: postgres
PG_PORT: 5432
PG_DB: waline
PG_USER: waline
PG_PASSWORD: ${PG_PASSWORD}
PG_PREFIX: "wl_"
SECURE_DOMAINS: "你自己的域名"
# 新增:Cloudflare Turnstile
TURNSTILE_KEY: ${TURNSTILE_KEY}
TURNSTILE_SECRET: ${TURNSTILE_SECRET}同样建议把这两个值也挪到 .env 里统一管理,避免密钥直接写死在 docker-compose.yml 里被提交进版本库:
PG_PASSWORD=你的强密码
TURNSTILE_KEY=你的SiteKey
TURNSTILE_SECRET=你的SecretKey5.3 重启服务生效
docker compose up -d
docker compose logs --tail=50 walineWaline 的前端评论组件在初始化时会自动从 serverURL 拉取配置(包括是否启用了 Turnstile),所以博客前端一般不需要额外改代码,重启容器让新的环境变量生效即可。如果博客前端是手动传参初始化的,且没有走默认拉取配置的逻辑,可以额外确认一下有没有强制覆盖掉验证码相关的初始化参数。
5.4 验证
- 打开博客文章页,评论框在提交评论时下方应该会出现 Turnstile 的小组件。
- 提交一条测试评论,确认能正常发出。
- 如果组件不显示或报错,沿用前面的排查思路:
docker compose logs waline:看是否因为TURNSTILE_SECRET没读到导致校验报错- 浏览器控制台:看有没有跨域或域名不匹配的报错——最常见的坑是 Cloudflare 后台的 Domain 里漏加了 Waline 服务端子域名
- 确认
.env和docker-compose.yml在同一目录下,避免变量没读到
6. 注意事项
-
私有化部署后,第一个注册的用户会自动成为管理员,我们需要打开
https://waline.dearom.com/ui/register去注册自己的管理员账户,防止被抢注了,(虽然你可以在云服务器的后台修改数据库的数据来强制自己成为管理员)。 -
私有化部署解决了"服务说挂就挂""数据库说跑路就跑路"的问题,但数据全部在自己手上之后,备份责任也全部落在自己身上了。接下来打算再做两件事:
-
给
/srv/waline/db做定时快照或者pg_dump备份,并同步一份到对象存储,避免服务器本身出问题导致评论数据丢失。 -
加一个简单的监控(比如容器健康检查告警),出问题能第一时间知道,而不是等读者反馈评论功能挂了才发现。
-
-
需要在腾讯云的安全组那里放行443端口和80端口,腾讯云是白名单模式,新机器只开了22端口和ping端口。此外在放行443端口和80端口之后,把来源替换成Cloudflare的IP地址范围(IPS),只允许Cloudflare的IP访问自己的端口。注意需要
waline.dearom.com这条 DNS 记录在 Cloudflare 里必须是"已代理"状态(橙色云朵),意味着你的域名必须使用Cloudflare的DNS服务器。 -
同时把22端口和ICMP的来源也限制成自己的住宅IP(虽然住宅IP是一个动态范围,但是日常可以通过腾讯云安全组的"云端登录"豁免机制)这样操作下来,别人就很难攻破的服务器了。
评论