这是「Docker 好项目」系列的第 6 期。每期挑一个真正能在自己服务器上跑起来、并且能解决具体问题的项目,给出能直接复制的配置。
博客写了几十篇,你大概知道哪篇被看得最多——但你真的知道吗?还是只靠评论数猜?
多数人的第一反应是装 Google Analytics。但对自建博客来说它有三个麻烦:国内访问 analytics 脚本经常加载失败、要挂 cookie 同意横幅、数据存在别人的服务器上。
Umami 换了个思路:一个自建的网站统计,不用 cookie、不收集个人数据、跟踪脚本只有 2KB,装在你自己服务器上,数据在你自己库里。MIT 协议,开源免费。

它到底能干什么
- 基础指标:访问量、访客数、页面浏览量、跳出率、平均停留时长
- 来源分析:来源域名、来源链接、UTM 参数
- 访问明细:页面、入口页、出口页、国家/地区、浏览器、操作系统、设备类型
- 实时数据:最近 30 分钟的在线访客
- 自定义事件:追踪按钮点击、表单提交、下载等任意行为
- 多站点 + 团队:一个面板管多个网站,可以开子账号并限定只能看某个站
- 保留数据:支持设置数据保留期,自动清理老数据
它不做的是:不做热图、不做会话录屏、不做漏斗转化分析。Umami 的定位是干净、轻、够用的访问统计,不是行为分析平台。
部署前准备
- 内存:Umami 本身约 230MB + PostgreSQL 约 155MB,准备 1GB 空闲内存比较稳妥
- 数据库:只支持 PostgreSQL(v3 起已经不支持 MySQL,网上老教程里的 MySQL 版配置直接跳过)
- 域名:可选。只在本机看的话不用域名,不过建议配一个,方便随时查数据
- 端口:默认 3000
docker-compose 配置
mkdir -p /opt/umami && cd /opt/umami
先生成两个密钥,别用示例里的占位符:
echo "数据库密码:$(openssl rand -hex 16)"
echo "APP_SECRET:$(openssl rand -hex 32)"
把输出记下来,填进下面的文件。新建 docker-compose.yml:
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
environment:
DATABASE_URL: postgresql://umami:这里换成数据库密码@umami-db:5432/umami
APP_SECRET: 这里换成APP_SECRET
DISABLE_TELEMETRY: "1"
depends_on:
umami-db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/heartbeat || exit 1"]
interval: 5s
timeout: 5s
retries: 5
umami-db:
image: postgres:16-alpine
container_name: umami-db
restart: unless-stopped
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: 这里换成数据库密码
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami"]
interval: 5s
timeout: 5s
retries: 5
启动:
docker compose up -d
docker compose logs -f umami
第一次启动会自动跑数据库迁移,日志里出现 Ready 就成了。数据库没就绪时 Umami 会退出重启,这是正常的,等十几秒再看。
每个配置项为什么这么写
| 配置 | 说明 |
|---|---|
postgresql-latest |
官方维护的 PostgreSQL 版滚动 tag。想要版本可复现就换成具体版本(当前 ghcr 上可见的最新为 postgresql-v2.16)。不要用不带后缀的裸 tag,那是 MySQL 版 |
DATABASE_URL |
里的密码必须和下面 POSTGRES_PASSWORD 完全一致,两处不一致是最常见的启动失败原因 |
APP_SECRET |
用来给登录态签名,至少 32 位随机字符。泄露等于别人能伪造你的登录会话;随便改一次会强制所有人重新登录 |
127.0.0.1:3000:3000 |
统计面板能看到你所有网站的流量,不该直接暴露公网。绑本机后用反代对外 |
DISABLE_TELEMETRY |
关掉匿名使用数据上报。自建的意义就是数据不出门,这个顺手关掉 |
./pgdata:/var/lib/postgresql/data |
所有统计数据在这里,这是唯一要备份的东西 |
depends_on: service_healthy |
确保数据库真正就绪再启动 Umami,避免启动即崩溃的死循环 |
healthcheck |
/api/heartbeat 是官方健康检查接口,返回 {"ok":true} 即正常 |
首次登录
浏览器打开 http://服务器IP:3000(本机则用 http://localhost:3000)。
默认账号:
- 用户名:
admin - 密码:
umami
登录后立刻去 Settings → Profile 改密码。这套默认凭据是公开写进文档的,而统计面板能看出你所有站点的流量结构。
添加网站,拿到跟踪代码
Settings → Websites → Add website:
| 字段 | 填什么 |
|---|---|
| Name | 起个名字,比如「个人博客」 |
| Domain | www.你的域名(多个域名用逗号分隔) |
保存后会生成一段跟踪代码,形如:
<script defer src="https://stat.你的域名/script.js" data-website-id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"></script>
data-website-id 是每个网站唯一的标识,不要复制错——贴错 ID 的表现是:脚本正常加载、但后台一条数据都没有。
接进 Halo 博客
如果你也用 Halo,最省事的做法是后台 → 设置 → 代码注入 → 页脚 HTML,把上面那段 script 粘进去保存。
刷新博客前台,然后打开 Umami 的实时面板,应该能看到自己这一条访问记录。看到就通了。
用了 Cloudflare 的话,注意别把
/script.js和/api/send两个路径拦掉,那会导致「脚本 200 但数据为零」。
避开广告拦截器
主流广告拦截规则默认就会屏蔽 script.js 和 umami 这个路径。你的访客装了 uBlock,他这次访问就统计不到。改两个名字就能大部分绕开:
environment:
TRACKER_SCRIPT_NAME: "stats"
COLLECT_API_ENDPOINT: "/api/collect"
重启后跟踪代码变成:
<script defer src="https://stat.你的域名/stats" data-website-id="你的ID"></script>
追踪自定义事件
想知道「有多少人点了下载按钮」「有多少人提交了表单」,可以在按钮上加一个属性,什么都不用写:
<button data-umami-event="download-resume">下载简历</button>
也可以带上下文数据:
<button data-umami-event="buy" data-umami-event-plan="pro">升级专业版</button>
需要更复杂的判断就用 JS 调用:
umami.track('signup', { plan: 'pro', source: 'homepage' });
事件会出现在后台的 Events 页面,可以按事件名和属性值看。
套上域名与 HTTPS
Umami 本身不处理证书,用第 2 期讲过的 Nginx Proxy Manager 反代即可:新建 Proxy Host,域名填 stat.你的域名,Forward 指向 127.0.0.1:3000,勾上 SSL 一键签证书。
用 1Panel 的话更直接:网站 → 反向代理 → 新建,代理到 127.0.0.1:3000。
唯一要注意的是 WebSocket——实时面板靠它刷新,反代时记得开启。
备份
统计数据全在 PostgreSQL 里,用 pg_dump 导出最稳:
cd /opt/umami
docker compose exec -T umami-db pg_dump -U umami umami | gzip > umami-backup-$(date +%F).sql.gz
恢复:
gunzip -c umami-backup-2026-09-19.sql.gz | docker compose exec -T umami-db psql -U umami -d umami
挂到 1Panel 的计划任务或 crontab,每天凌晨跑一次,只保留最近 14 份。
踩坑清单
- 容器一直重启:先看
docker compose logs umami,九成是DATABASE_URL里的密码和POSTGRES_PASSWORD不一致 - 改了 APP_SECRET 后全部登出:属于正常行为,重新登录即可
- 后台一条数据都没有:依次查——跟踪代码的
data-website-id对不对、浏览器控制台有没有报错、Cloudflare 有没有拦/script.js - 访客数据明显偏低:大概率是广告拦截器,按上面改名即可
- 从 v2 升 v3 前先备份:v3 有破坏性变更且不再支持 MySQL,务必
pg_dump之后再动 - 别把 3000 端口直接开到公网:统计面板等于你网站的流量地图,绑本机 + 反代
小结
Umami 是那种「装上就不用管」的基础设施:2KB 脚本不影响博客加载速度,没有 cookie 就不用挂同意横幅,数据在自己库里想导就导。
适合:有自建博客或小站、想要干净访问数据、不想要 cookie 弹窗的个人站长。 不适合:需要热图、录屏、漏斗归因的运营分析场景——那是另一类工具(Matomo、Plausible 或商业 SaaS)的领域。
下期继续聊另一个 Docker 项目。
项目地址:https://github.com/umami-software/umami 官方文档:https://umami.is/docs