为什么我放弃了 Umami Cloud?记一次在威联通 NAS 本地搭建 Umami 统计与前端接入的完整折腾记录

之前博客一直用 Umami 官方的 Cloud 免费版做流量统计。虽然官方每月给的 10 万次事件配额对个人独立站点来说完全够用,但免费版有几个绕不过去的硬伤:数据只保留 6 个月、最多只能绑定 3 个网站,而且官方域名在部分网络环境下偶尔会被广告拦截插件直接屏蔽。

既然家里有一台 7x24 小时开机的 QNAP NAS,不如直接把整个统计服务搬回本地。数据存自己的 PostgreSQL 数据库,保留时间无上限,站点随意加,顺便把整个折腾过程和选型思考梳理沉淀下来。

方案选型:官方云端 vs 免费 Serverless vs NAS 自建

在动手之前,我评估了三种常见方案:

方案架构构成优点缺点 / 放弃理由
Umami 官方 Cloud官方托管 SaaS零维护,注册即用仅存 6 个月数据,限 3 站,公网域名易被 Adblock 规则命中
Cloudflare + NeonCF Pages + Neon Postgres零服务器成本,全球 CDN 加速Serverless 数据库存在冷启动延迟,配置跨平台环境较繁琐
本地 NAS 自建QNAP Docker + 本地 Postgres100% 数据掌控,永久保留,无站点限制,速度极快需要自己解决内网穿透与数据库持久化

对于已经有常开 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,其实它的核心作用有两个:
    1. 签名登录会话:验证后台登录状态,修改后所有已登录设备会被强制登出。
    2. 访客身份哈希(不依赖 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>

在博客端,通常有三种使用思路:

  1. 纯后台静默统计:前台不放任何组件,只有自己登录后台看图表。
  2. 公开统计看板:在 Umami 网站设置中开启 Share URL,生成免登录的只读看板,通过 <iframe> 嵌入到博客的独立关于页或统计页。
  3. 页脚极简计数:利用 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,既解决了数据保留时长和隐私自主权的问题,又拥有了完全不输商业产品的可视化仪表盘,算是一次投入产出比极高的内网折腾。