最近我把博客的访问统计从第三方平台换成了自托管的Umami。说实话,这个决定纠结了一阵子,但实际用下来,本地部署Umami并把分析面板开放到公网访问,这件事本身并不复杂。真正花时间的是理解“外部访问”这条链路里每一步在干什么、为什么要这样做。这篇就写给不想被平台规则和数据隐私问题牵着走的朋友:我会从零开始,完整走一遍Umami的本地部署流程,再重点讲清楚让公网用户能访问到你的分析服务需要打通哪些环节、踩过哪些坑。
1. 为什么选Umami:自托管网站分析的价值与方案对比
1.1 自建分析工具的动机:隐私、成本与控制权
做个人网站或者小团队产品的朋友应该都有体会:第三方统计工具虽然方便,但要么是脚本越来越重、拖慢页面加载,要么是数据模型的规则不透明,后台指标跟你实际流量根本对不上。更不用说其中一些平台天然需要把用户访问数据传到别人的服务器上。GDPR和国内个人信息保护的相关规范出来之后,很多人开始意识到:与其让第三方收集访客数据,不如把统计能力掌握在自己手里。
Umami解决的就是这个痛点。它是一款开源、轻量、注重隐私的网站分析工具。部署后,你可以拿到完整的访问量、来源、设备、语言、地理位置等指标,但不需要在页面里挂沉重的跟踪脚本,也不会把你的访客数据卖给第三方。数据存在你自己的数据库里,面板展示完全由你控制。这个“所有权”的价值,用过一次就很难回去。
1.2 Umami与其他自托管统计工具的关键差异
除了Umami,常见的自托管思路还有Piwik/Matomo、Plausible、GoAccess等。我实际对比过这几类的部署复杂度和日常使用体验:
| 方案 | 数据存储 | 部署难度 | 资源占用 | 适合场景 |
|---|---|---|---|---|
| Plausible | ClickHouse/PostgreSQL | 中等偏上 | 较高 | 追求开源协议更宽松、要长链路数据 |
| Matomo | MySQL | 中等 | 较高 | 需要复杂转化分析和老牌生态 |
| GoAccess | 日志文件 | 低 | 极低 | 只分析服务器日志,不需要前端脚本 |
| Umami | PostgreSQL | 低 | 很低 | 快速自托管、轻量、界面现代 |
单看部署门槛和日常维护成本,Umami是综合最优的。它默认用PostgreSQL存数据,前后端一体,Docker Compose一条命令就能跑起来,资源占用非常低。对于一台2G内存的小主机来说,整个Umami站跑起来加数据库,大概只占几百兆,压力不大。
1.3 本地部署的三种形态怎么选
所谓“本地部署”,实际操作里有三种常见形态:
- 纯内网部署:只在局域网内跑,自己开发调试时看数据,外网完全不开放。
- 公网VPS部署:程序部署在云服务器上,通过公网IP直接访问。
- 家庭/办公室本地部署:程序跑在自己家里的设备上,再通过路由器和域名把服务暴露到公网。
这篇标题里说“本地部署”并“实现外部访问”,我默认的场景是第三种:设备在自己手里,数据不出门,但需要让外网能访问到这个分析面板。公网VPS部署方案相对简单,因为IP是固定的,端口放行即可。真正麻烦的是家庭网络环境,一个是公网IP不总是固定的,另一个是涉及端口转发和动态DNS。后面章节我会把这两种情况的操作都讲清楚。
2. 部署前的准备:硬件、系统与依赖环境
2.1 设备选型:一台长期开机的机器是底线
Umami对硬件的要求不高,但有一个硬性条件:机器要能长期开机。网站分析讲究连续性,数据一旦断供,中间的访问记录就永远补不回来。我最初用一台旧笔记本来跑,散热和功耗都不太理想,后来换成了小主机方案,稳定性和功耗表现都好很多。
如果你手头有树莓派、迷你主机、旧PC甚至NAS,都可以。只要满足以下条件:
- CPU:x86_64或ARM64都可以,轻松跑满。
- 内存:最低1GB,建议2GB以上,主要消耗在PostgreSQL。
- 磁盘:占用不大,但建议给Docker数据盘留出10GB以上余量。
- 系统:Debian/Ubuntu系最省心,CentOS和Fedora也能跑,但包管理命令不同。
操作系统我推荐Ubuntu 22.04 LTS或者Debian 12。原因只有一个:Docker官方文档对这两个系统的支持最完整,遇到问题搜索到的资料也基本都在这个体系里。
2.2 安装Docker与Docker Compose插件
Umami官方推荐通过Docker部署,这也是我个人最推荐的方式——环境隔离、升级方便、删除干净。安装Docker直接用官方脚本,一行搞定:
curl -fsSL https://get.docker.com | bash装完之后启动服务并设置开机自启:
sudo systemctl enable --now dockerDocker Compose方面,新版Docker已经内置了Compose v2插件,不需要单独安装。验证方式很简单:
docker compose version如果输出里有诸如“Docker Compose version v2.x.x”之类的版本信息,就说明Compose可用了。很多人喜欢直接用docker-compose命令(带横线),那是旧的Compose v1二进制版本,现在的新版本推荐用docker compose(空格分隔)。后面的操作我全部用新命令格式。
2.3 域名准备:强烈建议,而不是可选项
访问Umami的方式有两种:直接用IP加端口访问,或者用域名加HTTPS访问。我强烈建议尽早注册一个域名并解析到你的服务上。原因不只是好看,而是后续涉及反向代理、HTTPS证书、追踪脚本的稳定性,域名方案都远优于裸IP。
如果暂时没有域名,先用IP加端口也能跑通整个流程,但后续如果做HTTPS或者CDN,域名是绕不开的。域名注册找一个常见的注册商即可,注意选支持API或自动续费的,省心一些。
DNS解析这块,如果你有固定公网IP,直接添加一条A记录指向该IP;如果是动态公网IP,则需要结合后面的DDNS章节操作,用DDNS服务商提供的域名或者泛解析通配符方案来适配。
3. 核心实操:用Docker Compose部署Umami完整流程
3.1 编写docker-compose.yml:逐行解读
Umami官方提供一个现成的Compose配置,但我不建议直接照抄,因为默认配置里的PostgreSQL数据没有挂载到宿主机,容器一删数据就没了。正确的思路是手动改写好数据卷,把数据库文件真正落到宿主机磁盘上。
下面这个是我在用的配置,可以直接保存为docker-compose.yml:
version: '3' services: postgres: image: postgres:15-alpine container_name: umami_db restart: always environment: POSTGRES_USER: umami POSTGRES_PASSWORD: chage_this_password POSTGRES_DB: umami volumes: - ./data/postgres:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U umami"] interval: 5s timeout: 5s retries: 5 umami: image: ghcr.io/umami-software/umami:postgresql-latest container_name: umami_app restart: always ports: - "3000:3000" environment: DATABASE_URL: postgresql://umami:chage_this_password@postgres:5432/umami DATABASE_TYPE: postgresql HASH_SALT: any_random_long_string_here APP_SECRET: another_random_long_string_here depends_on: postgres: condition: service_healthy有几处需要特别解释:
POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB:PostgreSQL初始化时自动创建的用户、密码和数据库名。密码务必改成足够强的随机字符串,这里只是示例。DATABASE_URL:Umami应用连接数据库的完整URL。注意这里的@postgres:5432不是IP,而是Compose服务名,Docker内部会解析成对应容器的IP。所以这个URL必须在Compose环境中才有效。HASH_SALT:Umami用来给密码和Token做哈希的盐值。安装后如果改这个值,已有的账号密码会全部失效,所以一定要设好并备份。APP_SECRET:新版Umami用来签名会话令牌的密钥,同样要设置,否则部分功能会有安全风险。healthcheck:让Compose先确认数据库健康了再启动应用,否则应用启动时会连不上数据库直接退出。- 端口部分:
"3000:3000"表示把容器内3000端口映射到宿主机3000端口。如果宿主机3000端口被占用,可以改成别的,比如"8080:3000"。
3.2 启动服务并确认状态
配置文件保存好后,在同一个目录下执行:
docker compose up -d第一次执行会拉取镜像,时间取决于网络环境。拉取完成后,用下面的命令看状态:
docker compose ps正常的话输出里两个服务的状态都应该是Up。然后打开浏览器,访问http://你的服务器IP:3000。
这里有一个比较常见的坑:如果页面打不开,先不要怀疑配置,先确认容器状态;如果容器状态显示Restarting,大概率是数据库连接的问题,后面常见问题章节会详细排查。
3.3 首次登录与初始化设置
Umami首次打开会进入登录页。默认账号和密码都是admin,登录后第一步强制要求修改管理员密码,这是Umami内置的安全策略。
登录之后需要做三件事:
- 第一步,改密码。系统会直接引导,依次填入当前密码、新密码、确认密码。
- 第二步,创建网站。左侧菜单选择“网站”,点右上角“添加网站”,填写网站名称和域名。域名这一栏建议填你正式使用的访问域名,同一个域名可以多次添加对应不同子路径,但一般一个站点就够了。
- 第三步,获取跟踪代码。在“网站”列表中找到刚创建的站点,点“编辑”图标,底部会有一行“脚本地址”。这个就是待会儿往自己网站里粘贴的跟踪代码。Umami生成的是普通JS脚本,直接贴到网页
<head>或<body>里即可,格式类似:
<script async defer src="https://你的域名/script.js">server { listen 80; server_name yourdomain.com; location / { proxy_pass http://127.0.0.1:3000; 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; } }第二步,签发HTTPS证书。我用certbot工具,一条命令自动完成:
sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d yourdomain.comcertbot会自动修改Nginx配置并启用HTTPS,证书即将到期时还会自动续期。需要提前确认的是:你的域名A记录已经指向这台服务器的公网IP。
如果你不想用Nginx,Caddy是更简单的选择。Caddy自带自动HTTPS,配置文件极简。下面的Caddyfile就能实现同样效果:
yourdomain.com { reverse_proxy 127.0.0.1:3000 }我个人实际测试后发现,在纯反向代理和HTTPS场景下Caddy确实更省心,但Nginx可调参数更多,看你习惯用哪个。
4.5 没有公网IP的本地服务怎么开放
没有公网IP不意味着完全没法做外部访问。国内很多家庭宽带不给公网IP,但你的需求往往是远程管理、临时演示、或者自己在外出时看一眼统计面板。这种情况下,可以用内网隧道工具,把本地3000端口暴露到一个临时或固定域名上。
这一类的工具名称很多,例如frp、ngrok、Cloudflare Tunnel等,它们的作用都是把内网端口安全地映射到公网域名。我把这理解为“给内网服务装一个远程呼叫通道”,目的是访问自己的服务,用于远程调试和自己管理,这是完全正常的开发运维手段。
以frp为例,你需要一台有公网IP的小机器(比如云服务器)作为中转,然后在服务器端配置frps,在本地设备配置frpc。配置逻辑是把本机的3000端口映射到服务器的一个外部端口上,之后访问服务器IP:映射端口即可。如果配置了域名和HTTPS,体验跟有公网IP无差别。
Cloudflare Tunnel则更简单,不需要固定公网IP中转,直接在本地安装cloudflared并配置隧道,把流量交给Cloudflare的边缘网络。好处是不用单独准备公网服务器,Cloudflare免费套餐够用,坏处是域名要托管在Cloudflare上,且国内网络的连通性需要自己测试。
需要特别明确一点:这套隧道方案只用于开放你自己部署的服务,不是也不应该用于访问任何外部受限资源,这点在使用时心里要有数。合规使用,工具本身是无害的。
5. 安全加固与数据备份:别把面板裸奔在公网上
5.1 防火墙规则:先拒绝再放行
把服务暴露到公网之后,最忌讳的做法是全端口裸奔。正确姿势是最小化放行:默认拒绝所有入站,只放行需要用到的端口。
在Ubuntu上用UFW操作很方便:
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable如果你用路由器端口映射,而非云服务器,那客户端防火墙也建议照此办理。注意放开22端口时如果不是仅限内网管理,建议配合SSH Key登录,并禁用密码登录。
5.2 访问控制与基础认证
Umami本身有账号体系,默认的admin账号改完密码后,安全风险已经降了一大半。但如果你是管理员,又经常在外网登录管理后台,建议再做一层保护:
- 开启Nginx层面的Basic Auth,给整个面板URL加一道密码。这个对暴力破解账号密码非常有效。
- 限制管理后台的来源IP。如果固定办公场地,在Nginx里加一条
allow your_source_ip; deny all;即可,其余人访问全部返回403。 - 开启Umami自带的“禁用创建账号”选项(如果有多个用户才需要关注),并定期更换密码。
5.3 PostgreSQL数据备份与恢复
数据是无价的。PostgreSQL容器一旦损坏或者误删,恢复起来比重新部署麻烦得多——所以必须提前规划备份。
备份其实一条命令的事:
docker compose exec postgres pg_dump -U umami umami > backup.sql恢复时先把备份文件拷到宿主机,然后:
cat backup.sql | docker compose exec -T postgres psql -U umami umami简单粗暴但有效。如果追求自动化,可以在宿主机上写一个cron脚本,每天晚上执行一次备份,并压缩保留最近30天:
0 2 * * * cd /home/user/umami && docker compose exec -T postgres pg_dump -U umami umami | gzip > /home/user/backups/umami_$(date +\%Y\%m\%d).sql.gz我在实际操作中还发现,恢复数据时必须保证Umami应用处于停止状态,否则可能会有连接占用导致恢复报错。正确顺序是docker compose stop umami→ 恢复数据库 → 重新docker compose up -d。
6. 已有环境中的坑:常见问题与排查技巧实录
6.1 数据库连接失败导致容器无限重启
这是最典型的开局问题。现象是docker compose ps里umami容器不断重启,日志报数据库连接错误。排查步骤:
- 检查
DATABASE_URL里的密码和POSTGRES_PASSWORD是否一致。 - 检查
depends_on有没有加condition: service_healthy。没有这句话的话,应用可能在数据库初始化之前就尝试连接。 - 检查PostgreSQL容器本身是否健康:
docker compose logs postgres | tail -50如果看到Permission denied或者data directory相关错误,大概率是数据卷目录权限问题,在挂载的./data/postgres目录上执行chown -R 1000:1000再重启。
6.2 端口映射不生效:局域网能访问,外网不行
局域网内能用http://内网IP:3000打开面板,但公网IP访问不通。按顺序排查:
- 首先确认路由器WAN口IP是不是真正的公网IP。用手机流量访问
http://WAN口IP:映射端口,如果打不开,说明运营商做了NAT。 - 确认端口映射协议为TCP而不是UDP。
- 确认路由器有没有开启NAT回环(NAT Loopback)。有些路由器不支持从内网用公网IP访问自己局域网里的服务,但外网用户是可以正常访问的。这种情况下用手机流量测才能反映真实状态。
- 检查Umami容器监听的是不是
0.0.0.0:3000。可以用docker compose exec umami netstat -tlnp查看。如果只监听127.0.0.1,则端口映射无效,需要检查Compose配置里是否无意中写了127.0.0.1:3000:3000。
6.3 追踪脚本不生效,统计数据全是0
代码贴上了,页面也加载了,但Umami面板看不到任何访问量。这个问题常见原因有三个:
- 网站域名和地址填错了。在Umami网站设置里填入的域名,跟实际追踪页面域名做匹配时会做校验。如果填的是
example.com,但实际页面是www.example.com,就会不匹配。 - 页面里同时开启了广告拦截插件。绝大多数知名统计脚本都在过滤列表里,Umami相对小众,但也不排除。建议在无痕窗口或关闭拦截插件状态下测试。
- HTTPS页面加载了HTTP脚本。浏览器控制台会直接报Mixed Content错误,修改为HTTPS地址即可。
排查时打开浏览器DevTools的Network面板,看script.js请求是否正常返回200。如果返回403或404,去Umami后台检查按钮里站点ID是否和脚本里>
医学图像分割实战:CT白质与视网膜血管分割的深度学习方案
手头同时接过两个活儿,一个要把脑部CT里的大脑白质分割出来,另一个要从视网膜眼底照片里把血管网络一根根提取出来。当时看着这两张完全不同的图像,我还以为是两个独立的项目,做到一半才反应过来,它们背后的方法论、网…
单图3D重建+机器人抓取:ZeroGrasp从原理到落地的完整复盘
你真准备把“摄像头拍一张图,机械臂伸手把目标物体抓起来”这句话做成一个能跑的 demo,会发现它几乎是机器人学里最反直觉的任务之一。ZeroGrasp 是我给一套单图 3D 重建 机器人抓取学习复现项目起的内部代号——不依赖多视角扫描,不预先给物…
轮式航位推算实战:轮速计与MEMS IMU融合定位从标定到调试
简介:本资源为基于轮式 MEMS IMU 的航位推算系统完整项目包,面向计算机、自动化、电子信息等专业的在校学生、教师及企业研发人员,也适合作为毕设、课设或项目立项的参考方案。项目仅使用一个安装在车轮上的 IMU 实现轮式机器人航位推算&…
PaviaU高光谱分类实战:SVM、CNN、KNN三条路线对比与避坑指南
简介:本资源面向遥感图像处理与机器学习入门者,提供在Matlab环境下对高光谱数据集PaviaU进行分类的完整实践方案,重点对比支持向量机、卷积神经网络与K近邻三种算法的分类效果。压缩包共35个文件,以27个m脚本、7个mat数据文件和1个…
千兆以太网一致性测试实战:从示波器选型到PHY物理层验证
信号一致性这种问题我是真真切切折腾过的。当时项目里用到一颗常见的商用千兆PHY芯片,原理图照着芯片厂商的参考设计画,PCB走线也严格控制了差分阻抗,功能测试一切正常,结果一到量产前的压力测试就露馅:网口指示灯常亮…
VC++ GIS源码实战:从编译到空间索引优化
简介:这份资源是面向GIS开发初学者与进阶者的VC完整项目源码,基于MFC框架实现,适合用于课程设计、毕业设计参考或自学地理信息系统底层原理。压缩包共511个文件,约12.12MB,以gif、bmp、jpg等图像资源,cpp、…