为什么我放弃了 Umami Cloud?记一次在威联通 NAS 本地搭建 Umami 统计与前端接入的完整折腾记录
之前博客一直用 Umami 官方的 Cloud 免费版做流量统计。虽然官方每月给的 10 万次事件配额对个人独立站点来说完全够用,但免费版有几个绕不过去的硬伤:数据只保留 6 个月、最多只能绑定 3 个网站,而且官方域名在部分网络环境下偶尔会被广告拦截插件直接屏蔽。
既然家里有一台 7x24 小时开机的 QNAP NAS,不如直接把整个统计服务搬回本地。数据存自己的 PostgreSQL 数据库,保留时间无上限,站点随意加,顺便把整个折腾过程和选型思考梳理沉淀下来。
方案选型:官方云端 vs 免费 Serverless vs NAS 自建
在动手之前,我评估了三种常见方案:
| 方案 | 架构构成 | 优点 | 缺点 / 放弃理由 |
|---|---|---|---|
| Umami 官方 Cloud | 官方托管 SaaS | 零维护,注册即用 | 仅存 6 个月数据,限 3 站,公网域名易被 Adblock 规则命中 |
| Cloudflare + Neon | CF Pages + Neon Postgres | 零服务器成本,全球 CDN 加速 | Serverless 数据库存在冷启动延迟,配置跨平台环境较繁琐 |
| 本地 NAS 自建 | QNAP Docker + 本地 Postgres | 100% 数据掌控,永久保留,无站点限制,速度极快 | 需要自己解决内网穿透与数据库持久化 |
对于已经有常开 NAS 的人来说,本地 Docker 部署显然是综合体验最好、最受控的方案。
为什么坚持用 YAML 编排(而不是图形界面搜镜像)
在 QNAP 的 Container Station 里面建容器,很多人习惯直接在搜索框搜镜像然后一路点下一步。但 Umami 属于典型的“Web 前端 + 关系型数据库”双容器协同架构,用 Compose YAML 部署有绝对优势:
- 启动依赖控制:Umami 必须依赖 PostgreSQL 先就绪。YAML 中的
depends_on属性可以确保数据库完全启动后再拉起 Web 容器,防止开机自启时应用因为数据库连接超时直接崩溃。 - 内网隔离与安全性:Docker 会自动为该应用创建独立的内部虚拟网络,Umami 可以直接通过
db:5432内部域名访问数据库,宿主机根本不需要把 5432 端口映射到局域网,减少端口暴露风险。 - 配置可复现:所有环境变量、挂载目录一行代码搞定,换机或备份时直接复制 YAML 就能满血复活。
QNAP NAS 实操部署
1. 准备目录与配置
先在 QNAP 的 Container 共享目录下新建文件夹 umami,确保数据库持久化路径存在。
打开 Container Station -> 应用程序(Applications) -> 创建,填入应用名 umami 并粘贴以下 YAML 配置:
version: '3'
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami-app
restart: always
ports:
- "3000:3000"
environment:
DATABASE_URL: postgresql://umami:umami_pass123@db:5432/umami
APP_SECRET: my-secret-salt-key-change-it
depends_on:
- db
db:
image: postgres:15-alpine
container_name: umami-db
restart: always
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: umami_pass123
volumes:
- /share/Container/umami:/var/lib/postgresql/data
2. 核心环境变量解析
DATABASE_URL:PostgreSQL 连接串。格式为postgresql://用户名:密码@db:5432/数据库名,这里的密码必须与下方POSTGRES_PASSWORD保持一致。APP_SECRET:系统的核心加盐密钥。很多人以为这只是普通的登录 Token,其实它的核心作用有两个:- 签名登录会话:验证后台登录状态,修改后所有已登录设备会被强制登出。
- 访客身份哈希(不依赖 Cookie):Umami 为了隐私不写 Cookie,它通过
访客 IP + User-Agent + APP_SECRET计算出一段不可逆的 Hash 来去重 UV。这个值部署好后尽量不要变动。
点击创建后,等待容器拉取并启动,浏览器访问 http://NAS_IP:3000 即可看到登录界面。默认管理员账号为 admin,密码为 umami(登录后第一件事是进个人资料修改初始密码)。
博客接入与展示方式
登录后台点击 Settings -> Websites -> Add website,填入博客名称和域名,系统会生成一个专属的 website-id。
在博客的全局 HTML 模板 <head> 中嵌入追踪代码:
HTML
<script defer src="https://你的统计域名/script.js" data-website-id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"></script>
在博客端,通常有三种使用思路:
- 纯后台静默统计:前台不放任何组件,只有自己登录后台看图表。
- 公开统计看板:在 Umami 网站设置中开启 Share URL,生成免登录的只读看板,通过
<iframe>嵌入到博客的独立关于页或统计页。 - 页脚极简计数:利用 Umami 的 API 拉取 PV/UV,挂在网站底部替代传统的不蒜子计数器。
踩坑记录与实用技巧
1. 排除博主自身的测试访问
自己经常写文章、调样式,频繁刷新会严重污染 UV 和 PV 数据。Umami 内置了前端禁用机制。
在经常用来访问博客的浏览器中,按 F12 打开开发者工具,在 Console(控制台) 中输入:
JavaScript
localStorage.setItem('umami.disabled', 1);
注意 Chrome 控制台安全拦截:
新版 Chrome 开启了防 Self-XSS 粘贴保护。如果直接粘贴代码被阻止,需先在控制台手动键入
allow pasting并回车,然后才能正常粘贴执行。
2. 官方云端数据要不要导入自建?
原本打算把之前在 Umami Cloud 的几万条访问记录导出来塞进自建数据库,研究了一圈后决定直接放弃:
- 官方导出的只是扁平化的 CSV 报告。
- 自建版的底层是 PostgreSQL 结构化数据库,涉及
session(会话表)和website_event(事件表)的外键关联与时间戳对齐。
如果用脚本强行清洗注入,花费的时间和维护成本很高。务实的做法是把官方 Cloud 账号保留作为历史归档,自建实例从零开始记录,省时省心。
3. 公网流量收集与防拦截
统计服务放在 NAS 上,访客要在公网正常上报数据,必须配置公网访问。
强烈推荐使用 Cloudflare Tunnel 进行内网穿透:
- 无需公网 IP,无需路由器开放 3000 端口映射。
- 在 Cloudflare Zero Trust 后台直接将子域名(如
analytics.yourdomain.com)反代到 NAS 的http://localhost:3000。 - 独立二级域名不仅加载速度快,还能有效避开绝大多数 Adblock 规则列表对公共统计域名的拦截。
4. 关于热力图(Heatmaps)功能的取舍
Umami v3.2 及以上版本自建已支持点击与滚动热力图功能。但需要注意:热力图需要加载专用的录制脚本并频繁向数据库写入坐标数据,这会导致数据库体积增长速度明显加快。
折腾小结
整个服务在 NAS 上跑起来非常轻量,PostgreSQL 加 Web 容器一共只占用不到 150MB 内存。配合自定义域名与 Cloudflare Tunnel,既解决了数据保留时长和隐私自主权的问题,又拥有了完全不输商业产品的可视化仪表盘,算是一次投入产出比极高的内网折腾。