家里那台 Beelink EQ13 买回来两年了。最初是为了 HAOS,跑在 PVE 上。顺手又起了一个 VM 把搁置多年的个人博客捡回来——继续跑 WordPress,通过 Cloudflare Tunnel 让外网能访问,当时感觉省了每个月好几美元的 VPS 费用,就很爽了。

最近开始折腾 LXC 跑 Docker 容器,冒出一个想法:要不把 WordPress 也容器化,不就少一个 VM 吗?

结果这一念之差,让我开始了一段绕远的迁移之旅,最后博客根本不在这台机器上跑了。

WordPress + Docker 这事不大对

找了一份 docker compose,MySQL、WordPress 容器、卷挂载一应俱全,看着挺完整。但是盯着挂载仔细看一眼:官方镜像的套路是起来之后把自己解压缩到挂载的卷上,整个 /var/www/html 都在卷里。那镜像有什么意义?以后升级照样是从 WordPress 自己的后台点一下,docker compose pull 根本不管事。

“正确"的做法是只挂 wp-content,核心代码留在镜像里。好,那挂哪些、留哪些,要一条一条想清楚,挂错了升级机制就坏掉。然后还要操心备份、版本兼容、SQL 注入——一个一年发不了几篇的个人博客,凭什么需要一整套动态后端?

Ghost 也看了一眼

顺手研究了一下 Ghost。专注博客和 newsletter,Node.js 写的,听起来比 WordPress 现代一截。打开官方 docker-compose 一看:Ghost 5 还能 SQLite,6.0 起强制 MySQL 8。整套自托管方案七八个服务——Ghost 本体、MySQL、ActivityPub Fediverse 集成、Tinybird Analytics……

产品力比 WordPress 强是真的,但容器化下来仍然是一坨需要运维的东西,并没有更轻松。

静态才是正确答案

Jekyll 也瞄了一眼,然后意识到:Jekyll 和 Hugo 输出都是静态 HTML,生产环境根本不需要任何进程在跑。博客这种东西,最终就是一堆 HTML 而已。

路线一下子清晰:Hugo 在本地写文章,推 GitHub,Cloudflare Pages 自动构建发布。家里那台 Beelink 一根手指都不用动。

旧文章迁过来

wordpress-to-hugo-exporter 导出,几百篇 Markdown 一下就出来了。HTML 没有全部转成 Markdown,所以 hugo.tomlmarkup.goldmark.renderer.unsafe = true 要开。

旧文章和新文章分开放,旧的在 content/wp/,新的在 content/posts/。两个让旧链接不死的细节:

  • 每篇旧文章 frontmatter 里有导出工具带来的 url: /archives/<id>,老链接完全不变。
  • 旧文章引用的图片放 static/wp-content/uploads/,文章里的图片路径一行都不用改。

两个 section 要同时出现在首页列表,需要在 hugo.toml 里告诉 PaperMod:

1
2
[params]
  mainSections = ["posts", "wp"]

感觉良好

写这篇文章的方式:编辑器(任选,再也不用操心 WordPress 的编辑器了)里新建文件夹和 index.md,写完 git push。Cloudflare Pages 监控 GitHub,自动发布,很快就能访问了。

跟 WordPress 比起来,少了:

  • 一个 VM
  • 一个 PHP runtime
  • 一个 MySQL 数据库
  • 一份要操心的备份
  • 一份要操心的安全更新

再见 WordPress! 想当年我入门 web 开发,好像还是从折腾 WordPress 插件开始的呢。但它终究已经不适合个人博客了。

感谢赛博活佛们。