这个小站终于稳定跑起来了。

它叫 毛毛虫的日记本,现在可以通过下面这个地址访问:

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 上构建网站并实现公网访问的完整过程。

慢慢写,慢慢长大。