1. 部署架构
三个组件,一条请求链路。Umami 只监听本机回环地址,公网流量全部由 Nginx 接管:
访问 /Umami/ 或加载采集脚本
HTTPS 终止 + 反向代理 /Umami/
Next.js 应用 + 采集 API
2. 环境准备
| 项目 | 要求 | 说明 |
|---|---|---|
| 操作系统 | Linux(amd64 / arm64) | 本文以 Alibaba Cloud Linux 3 为例 |
| Docker | 20.10+,含 Compose v2 | docker compose version 能输出版本号 |
| 内存 | ≥ 1GB 可用 | Umami 约 300~500MB,Postgres 约 200MB |
| 磁盘 | ≥ 5GB 可用 | 镜像约 1GB,数据量取决于访问量 |
| 端口 | 本机 3010 空闲 | 只监听 127.0.0.1,不对外开放 |
| 域名 / 证书 | 已配置 HTTPS | 本站使用 123one.online 的现有证书 |
3. Docker Compose 部署
新建 /opt/umami/docker-compose.yml,内容如下。 注意 BASE_PATH 必须与最终访问路径完全一致(本站为/Umami,大写 U、无结尾斜杠)。
services:
umami:
# 官方镜像不支持运行时 BASE_PATH,需用 --build-arg BASE_PATH=/Umami 自建
image: umami-123one:3.4.0
container_name: umami
restart: always
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
ports:
- "127.0.0.1:3010:3000"
volumes:
# 构建时用 SKIP_BUILD_GEO=1 时必需,否则 /api/send 会 500
- ./geo:/app/geo:ro
environment:
DATABASE_URL: postgresql://umami:${POSTGRES_PASSWORD}@db:5432/umami
APP_SECRET: ${APP_SECRET}
TWO_FACTOR_ENCRYPTION_KEY: ${TWO_FACTOR_ENCRYPTION_KEY}
BASE_PATH: /Umami
DISABLE_TELEMETRY: 1
DISABLE_UPDATES: 1
DEFAULT_LOCALE: zh-CN
DEFAULT_CURRENCY: CNY
depends_on:
db:
condition: service_healthy
mem_limit: 768m
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:3000/Umami/api/heartbeat || exit 1"]
interval: 10s
timeout: 5s
retries: 6
start_period: 60s
db:
image: postgres:15-alpine
container_name: umami-db
restart: always
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
TZ: UTC
volumes:
- umami-db:/var/lib/postgresql/data
mem_limit: 512m
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami -d umami"]
interval: 10s
timeout: 5s
retries: 6
volumes:
umami-db:同目录创建 .env。三个密钥都必须是随机值,且POSTGRES_PASSWORD不能包含未转义的 @ : / ? # 等 URL 特殊字符(建议只用十六进制)。
# /opt/umami/.env —— 权限设为 600,不要提交到 Git
POSTGRES_PASSWORD=把这里换成随机密码
APP_SECRET=把这里换成 openssl rand -hex 32 的结果
TWO_FACTOR_ENCRYPTION_KEY=把这里换成 openssl rand -hex 32 的结果执行初始化命令:
# 1. 建目录
sudo mkdir -p /opt/umami && cd /opt/umami
# 2. 写入 docker-compose.yml(内容见上一节)
sudo nano docker-compose.yml
# 3. 生成 .env(三个随机值一次写入)
umask 077
{
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)"
echo "APP_SECRET=$(openssl rand -hex 32)"
echo "TWO_FACTOR_ENCRYPTION_KEY=$(openssl rand -hex 32)"
} > .env
chmod 600 .env
# 4. 启动
sudo docker compose up -d
# 5. 等待健康检查通过
sudo docker compose ps
curl -fsS http://127.0.0.1:3010/Umami/api/heartbeat && echo " OK"4. Nginx 反向代理
在 server 配置块(443)里加入以下配置。 两个关键点:location 大小写与 BASE_PATH 一致、proxy_pass 结尾不要加斜杠。 另外不要给 /Umami 补斜杠做 301 —— Next.js 会把 /Umami/ 归一化为 /Umami,补斜杠会造成死循环。
# 注意:Umami 的 BASE_PATH 是大写 /Umami
# 不要写 location = /Umami { return 301 .../Umami/; }
# Next.js 会把 /Umami/ 归一化为 /Umami,补斜杠会造成无限重定向
location ^~ /Umami {
proxy_pass http://127.0.0.1:3010; # 结尾不要加 /,否则路径会被剥掉
proxy_http_version 1.1;
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_read_timeout 120s;
proxy_buffering off;
# 后台/登录页不需要被搜索引擎收录
add_header X-Robots-Tag "noindex, nofollow" always;
client_max_body_size 10m;
}保存后检查并重载:
# 先备份,再改配置
sudo cp /www/server/panel/vhost/nginx/120.26.18.73.conf \
/www/server/panel/vhost/nginx/120.26.18.73.conf.bak-$(date +%F)
# 把上面的 location 粘进 server { ... } 里,然后:
sudo nginx -t && sudo nginx -s reload
# 验证(三条都应返回 200 / 301)
curl -I https://123one.online/Umami
curl -I https://123one.online/Umami/login
curl -I https://123one.online/Umami/script.js5. 初始化后台
- 1登录后台
打开 https://你的域名/Umami/login,默认账号 admin / umami。
- 2修改密码
右上角头像 → 个人资料 → 修改密码。建议使用 16 位以上随机密码,并存进密码管理器。
- 3开启两步验证(可选)
同一页面开启 2FA。前提是部署时已设置 TWO_FACTOR_ENCRYPTION_KEY,否则无法启用。
- 4添加网站
设置 → 网站 → 添加网站,填写名称与域名。保存后复制生成的 Website ID。
- 5开启分享链接(可选)
网站设置 → Share URL,生成只读链接,适合给客户或合作方查看。
6. 接入统计代码
把采集脚本加到你的网站。脚本地址为https://你的域名/Umami/script.js,带 data-website-id 即可。不同框架的完整代码可以在概览页的接入代码生成器里一键生成、复制。
- 打开网站,浏览器 Network 里能看到 POST /Umami/api/send,状态 200
- Umami 后台「实时」页面出现当前访问
- 页面 URL、来源、设备信息显示正确
- 生产域名与 data-domains 配置一致
7. 日常运维
统计数据的价值在于「长期」——备份比升级更重要。建议每天自动备份,并至少每周确认一次备份文件可用。
# 手动备份(输出到 /opt/umami/backup/)
cd /opt/umami
mkdir -p backup
docker exec umami-db pg_dump -U umami umami \
| gzip > "backup/umami-$(date +%F-%H%M).sql.gz"
# 只保留最近 30 天
find backup -name "umami-*.sql.gz" -mtime +30 -delete加入 crontab(每天 03:10):
# 每天凌晨 3:10 自动备份,日志写到 /var/log/umami-backup.log
10 3 * * * cd /opt/umami && mkdir -p backup && docker exec umami-db pg_dump -U umami umami | gzip > "backup/umami-$(date +\%F).sql.gz" >> /var/log/umami-backup.log 2>&1 && find backup -name "umami-*.sql.gz" -mtime +30 -delete查看状态、升级与资源占用:
cd /opt/umami
# 查看状态 / 日志
docker compose ps
docker compose logs -f --tail=100 umami
# 重启应用
docker compose restart umami
# 升级(先备份,再拉新镜像)
docker compose pull
docker compose up -d
docker compose ps
# 资源占用
docker stats --no-stream umami umami-db需要恢复数据时:
# 恢复某个备份(会覆盖现有数据,务必先停应用)
cd /opt/umami
docker compose stop umami
gunzip -c backup/umami-2026-01-01.sql.gz \
| docker exec -i umami-db psql -U umami -d umami
docker compose start umami8. 故障排查
访问 /Umami 返回 502 Bad Gateway▾
原因:容器没起来、端口不对,或启动过程中数据库还没就绪。
处理:先 docker compose ps 看状态,再 docker compose logs --tail=100 umami 看报错。首次启动需要建表,通常等待 30~90 秒。若日志提示数据库连接失败,检查 .env 里的密码是否与 db 服务一致。
页面能打开,但样式和 JS 全部 404▾
原因:BASE_PATH 与 nginx 转发路径不一致,最常见的是大小写不同或 proxy_pass 多写了结尾斜杠。
处理:确认容器环境变量是 BASE_PATH=/Umami(大写 U、无结尾斜杠),nginx 用 proxy_pass http://127.0.0.1:3010;(结尾不加斜杠),且 location 前缀同样是大写 /Umami/。
登录后马上跳回登录页,或提示会话失效▾
原因:APP_SECRET 被更换、Cookie 域名不匹配,或浏览器缓存了旧的会话。
处理:APP_SECRET 一旦设置就不要随意更换。清理浏览器 Cookie 后重试;若近期改过 APP_SECRET,重新登录即可(旧会话会失效)。
后台看不到任何数据▾
原因:脚本没加载、Website ID 不对、域名被 data-domains 限制,或被广告拦截插件/CSP 拦截。
处理:打开网站控制台,看是否有 POST /Umami/api/send 请求及其状态码;确认 data-website-id 与后台一致;检查 data-domains 是否包含当前域名;确认 CSP 允许同域脚本。
统计里的时间比本地时间差 8 小时▾
原因:数据库推荐使用 UTC,看板按浏览器时区展示;如果服务器/数据库时区混乱就会出现偏差。
处理:Postgres 设置 TZ=UTC,容器内保持默认;看板右上角切换时区即可。不要在多个服务里混用 Asia/Shanghai 与 UTC。
内存不足,容器被 OOM 杀掉▾
原因:小内存服务器同时跑多个 Node 服务,或 Postgres 缓存占用过高。
处理:compose 里限制 mem_limit(本站为 umami 768m、db 512m),关闭不用的容器,必要时增加 swap 或升级内存。用 docker stats 观察峰值。
升级后无法启动 / 数据库迁移失败▾
原因:跨大版本升级未备份,或迁移过程中被中断。
处理:升级前先 pg_dump 备份。若迁移失败,回滚镜像版本(把 latest 改成具体版本号)并恢复备份,再按官方升级说明逐版本升级。
后台被大量登录尝试▾
原因:默认路径公开,存在自动化扫描。
处理:使用强密码并开启两步验证;用 fail2ban 对 /Umami/login 做频率限制;不要使用 admin/umami 默认口令。