这个小站终于稳定跑起来了。
它叫 毛毛虫的日记本,现在可以通过下面这个地址访问:
https://note.sharpcaterpillar.top
这篇文章记录一下这次在群晖 NAS 上搭建 Halo 个人站,并通过公网域名访问的完整过程。既是建站记录,也是给以后自己留一份排查文档。
一、为什么选择把网站部署到 NAS 上
一开始我并不是直接部署在 NAS 上的。
最初尝试过在 Windows 11 + WSL + Docker Desktop 里部署博客系统,也折腾过 Mix Space。虽然最终也能跑起来,但过程里遇到了不少问题:
前后端版本不兼容
主题和后端接口版本不匹配
环境变量不生效
容器重启后状态异常
数据库目录不明确
重启后出现重新初始化页面
反向代理、缓存、端口映射混在一起排查困难
后来我意识到,我的目的不是研究博客系统本身,而是想要一个能长期稳定写日记、写学习笔记、写实践经验的地方。
所以最终决定换成更稳妥的方案:
使用 Halo 作为博客/日记系统
使用 PostgreSQL 作为数据库
使用 群晖 NAS 作为长期运行环境
使用 Docker Compose 统一管理容器
使用自己的域名
note.sharpcaterpillar.top访问网站
群晖 NAS 本身就适合长期运行,数据目录也更清晰。相比 Windows WSL,放在 NAS 上更接近一个稳定的小型服务器环境。
二、最终部署结构
这次最终采用的结构如下:
浏览器
↓
https://note.sharpcaterpillar.top
↓
Cloudflare 域名解析
↓
阿里云宝塔 Nginx 反向代理
↓
http://work.toplink.wang:13147
↓
公司路由器端口映射
↓
群晖 NAS:13147
↓
Halo 容器:8090
↓
PostgreSQL 容器
内部服务结构是:
halo Halo 主程序
halo-postgres PostgreSQL 数据库
群晖上的数据目录是:
/volume1/docker/halo/halo2
/volume1/docker/halo/db
/volume1/docker/halo/docker-compose.yaml
其中:
/volume1/docker/halo/halo2
用于保存 Halo 的附件、主题、插件、运行配置等数据。
/volume1/docker/halo/db
用于保存 PostgreSQL 数据库文件。
这两个目录非常重要,后续备份时必须一起备份。
三、在群晖上准备目录
首先在群晖 NAS 上创建目录:
/volume1/docker/halo
/volume1/docker/halo/halo2
/volume1/docker/halo/db
可以通过群晖 File Station 创建,也可以通过 SSH 执行:
sudo mkdir -p /volume1/docker/halo/halo2
sudo mkdir -p /volume1/docker/halo/db
cd /volume1/docker/halo
这里我选择把所有 Docker 项目都放在 /volume1/docker 下面,这样后续查找、备份和迁移都更方便。
四、使用 Docker Compose 部署 Halo + PostgreSQL
这次没有直接在 Container Manager 里从镜像创建单个容器,而是使用“项目”的方式部署。
原因是:单独创建 halohub/halo 容器虽然简单,但数据库不够明确,后期容易出现数据不持久、重启后重新初始化等问题。
所以使用 docker-compose.yaml 一次性部署两个容器:
Halo
PostgreSQL
配置文件如下:
services:
halo:
image: halohub/halo:2.24
container_name: halo
restart: unless-stopped
depends_on:
halodb:
condition: service_healthy
networks:
- halo_network
volumes:
- /volume1/docker/halo/halo2:/root/.halo2
ports:
- "13147:8090"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8090/actuator/health/readiness"]
interval: 30s
timeout: 5s
retries: 5
start_period: 60s
environment:
- JVM_OPTS=-Xmx512m -Xms256m
command:
- --spring.r2dbc.url=r2dbc:pool:postgresql://halodb/halo
- --spring.r2dbc.username=halo
- --spring.r2dbc.password=SharpcaterpillarHaloDB2026
- --spring.sql.init.platform=postgresql
- --halo.external-url=https://note.sharpcaterpillar.top/
halodb:
image: postgres:15.4
container_name: halo-postgres
restart: unless-stopped
networks:
- halo_network
volumes:
- /volume1/docker/halo/db:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "halo"]
interval: 10s
timeout: 5s
retries: 5
environment:
- POSTGRES_PASSWORD=SharpcaterpillarHaloDB2026
- POSTGRES_USER=halo
- POSTGRES_DB=halo
- PGUSER=halo
networks:
halo_network:
这里有几个关键点。
第一,Halo 容器端口是:
ports:
- "13147:8090"
意思是:群晖 NAS 的 13147 端口映射到 Halo 容器内部的 8090 端口。
所以局域网内访问方式是:
http://NAS内网IP:13147
第二,数据库目录明确挂载到了:
/volume1/docker/halo/db:/var/lib/postgresql/data
这样 PostgreSQL 的数据会保存到群晖硬盘上,而不是只存在容器内部。
第三,Halo 的外部访问地址设置为:
--halo.external-url=https://note.sharpcaterpillar.top/
这个配置很重要。因为后续网站正式通过 note.sharpcaterpillar.top 访问,所以 Halo 必须知道自己的公网访问地址。
五、在群晖 Container Manager 中创建项目
在群晖 DSM 中打开:
Container Manager → 项目 → 新增
项目名称填写:
halo
项目路径选择:
/volume1/docker/halo
然后选择使用已有的 docker-compose.yaml。
创建完成后,Container Manager 会自动创建两个容器:
halo
halo-postgres
启动后检查容器状态,两个容器都应为运行中。
如果使用 SSH,也可以执行:
cd /volume1/docker/halo
sudo docker compose ps
正常情况下应能看到:
halo running
halo-postgres running
六、先在局域网测试访问
在公网访问之前,先在局域网内测试。
浏览器访问:
http://NAS内网IP:13147
例如:
http://192.168.8.20:13147
如果能看到 Halo 初始化页面,说明:
Halo 容器正常
PostgreSQL 容器正常
NAS 本机端口映射正常
Docker Compose 配置基本正确
这一步非常关键。局域网不通,就不要继续折腾公网。
七、修改公司路由器端口映射
局域网确认正常后,再配置公司路由器端口映射。
原来的公网访问入口是:
work.toplink.wang:13147
需要在路由器里把这个端口映射到群晖 NAS:
公网端口:13147
内网 IP:群晖 NAS 的内网 IP
内网端口:13147
协议:TCP
也就是:
work.toplink.wang:13147 → 群晖NAS:13147
这里要特别注意:NAS 的内网 IP 最好固定。
如果 NAS 的内网 IP 变了,公网端口映射就会失效。所以建议在路由器里给 NAS 做 DHCP 地址保留,或者给 NAS 设置固定 IP。
八、宝塔 Nginx 反向代理
我的域名 sharpcaterpillar.top 在 Cloudflare 上,最终访问地址希望是:
https://note.sharpcaterpillar.top
公网请求先到阿里云服务器上的宝塔 Nginx,再反向代理到公司网络的 work.toplink.wang:13147。
宝塔中创建站点:
note.sharpcaterpillar.top
然后配置反向代理,目标地址为:
http://work.toplink.wang:13147
注意这里是 http,不是 https。
反向代理大致结构是:
location / {
proxy_pass http://work.toplink.wang:13147;
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;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
}
这样,外部访问:
https://note.sharpcaterpillar.top
实际上会被代理到:
http://work.toplink.wang:13147
再由路由器转发到群晖 NAS。
九、通过正式域名初始化 Halo
前面局域网、路由器、宝塔都配置好之后,使用正式域名访问:
https://note.sharpcaterpillar.top
首次访问会进入 Halo 初始化页面。
初始化时填写:
站点标题:毛毛虫的日记本
用户名:sharpcaterpillar
昵称:犀利的毛毛虫
邮箱:自己的邮箱
后台地址是:
https://note.sharpcaterpillar.top/console
后续所有后台操作都通过这个地址完成。
这里我尽量避免再使用内网 IP 或临时地址初始化,因为正式域名和 Halo 的 external-url 保持一致,后续登录、附件链接、跳转地址会更清晰。
十、验证数据是否真正写入数据库
初始化完成后,必须马上验证数据是否真正落到了 PostgreSQL 里。
通过 SSH 登录群晖,执行:
sudo docker exec -it halo-postgres psql -U halo -d halo -c "select name from extensions where name like '/registry/users/%';"
正常应该看到类似:
/registry/users/anonymousUser
/registry/users/ghost
/registry/users/sharpcaterpillar
只要能看到:
/registry/users/sharpcaterpillar
就说明管理员用户已经真正写入 PostgreSQL 数据库。
这一步是为了避免再次出现“重启之后又回到初始化页面”的问题。
十一、安装主题 Serenity-Grace
初始化完成后,我选择了 Serenity-Grace 作为主题。
这个主题比较适合个人日记站,有一种清爽、梦幻、轻量的感觉。它支持首页头像、背景图、个人简介、社交图标、文章列表、关于页等配置。
在后台进入:
外观 → 主题
安装并启用 Serenity-Grace。
之后在主题设置里调整:
首页背景图
头像
显示名称
个性标语
个人简介
主题颜色
菜单
关于页
附件显示
目前站点的定位是:
记录生活、思考、技术与一些不成体系的灵感
所以整体风格尽量保持清爽、柔和,不做太复杂的商业化设计。
十二、配置菜单和分类
网站初期不需要太复杂,菜单先保持简单:
首页
文章
技术折腾
关于
后续可以增加:
日记
学习笔记
实践记录
读书笔记
管理思考
分类建议:
日记
学习笔记
实践记录
技术折腾
读书笔记
管理思考
标签可以自由一些,比如:
Halo
NAS
Docker
建站
Cloudflare
宝塔
PostgreSQL
软件开发
复盘
第一阶段最重要的不是把栏目设计得多复杂,而是能稳定写文章。
十三、附件上传和大文件问题
在测试附件上传时,我尝试上传了一个 200 MB 以上的视频文件。
通过公网域名上传时失败,提示文件太大。后来通过局域网访问 NAS 上传成功。
这说明问题不在 Halo,也不在 NAS,而是在公网链路中的上传大小限制。
公网链路中可能限制上传大小的地方包括:
Cloudflare
宝塔 Nginx
反向代理配置
请求超时时间
上传体积限制
后来判断:如果大文件超过 Cloudflare 免费代理的限制,就不能通过 Cloudflare 橙色云直接上传。更好的方式是:
大文件在局域网内通过
http://NAS内网IP:13147/console上传或者配置一个不走 Cloudflare 代理的上传域名
或者后续把大视频放到对象存储、MinIO、群晖文件服务中,再在文章里引用链接
对于日记站来说,图片和小文件放 Halo 附件库没有问题;大视频长期来看最好单独存储。
十四、备份方案
这次部署完成后,必须建立备份习惯。
最重要的目录是:
/volume1/docker/halo
它包含:
/volume1/docker/halo/halo2
/volume1/docker/halo/db
/volume1/docker/halo/docker-compose.yaml
可以做完整目录备份:
cd /volume1/docker
sudo tar -czf halo-full-backup-$(date +%Y%m%d-%H%M%S).tar.gz halo
也可以单独做数据库逻辑备份:
sudo docker exec halo-postgres pg_dump -U halo halo > /volume1/docker/halo-db-$(date +%Y%m%d-%H%M%S).sql
以后升级 Halo、换主题、改配置之前,都应该先备份。
这次折腾给我的最大教训就是:只要数据目录不清楚,后面一定会出问题。
十五、重启验证
部署完成后,我还需要做一次完整验证:
第一步,重启容器:
cd /volume1/docker/halo
sudo docker compose restart
然后访问:
https://note.sharpcaterpillar.top
确认网站还在。
第二步,检查数据库用户:
sudo docker exec -it halo-postgres psql -U halo -d halo -c "select name from extensions where name like '/registry/users/%';"
确认 sharpcaterpillar 还在。
第三步,重启 NAS,再重复测试。
只要重启 NAS 后仍然可以正常访问,后台账号还在,文章和附件都正常,说明这套部署方式才是真正稳定的。
十六、这次建站的收获
这次看起来只是搭了一个个人日记站,但实际经历了不少环节:
Docker 容器部署
PostgreSQL 数据持久化
群晖 NAS 目录管理
路由器端口映射
宝塔 Nginx 反向代理
Cloudflare 域名解析
Halo 初始化
主题安装和配置
附件上传限制排查
数据备份和重启验证
最深的感受是:个人网站不一定要复杂,但结构一定要清晰。
容器可以重建,主题可以更换,配置可以调整。
但数据目录一定要明确,数据库一定要持久化,备份一定要有。
否则看起来部署成功了,实际重启一次就可能回到初始化页面。
十七、最后
这个站终于从折腾状态进入了可以正常使用的状态。
它不是一个追求流量的网站,也不是一个复杂的技术博客。
它更像是一个属于自己的长期记录空间。
以后这里会写:
日记
学习笔记
技术折腾
项目实践
管理思考
读书记录
一些碎碎念
第一篇文章,就记录这次从 NAS 上构建网站并实现公网访问的完整过程。
慢慢写,慢慢长大。
评论交流
欢迎留下你的想法