在 Mac Mini 上 Self-Host Aptabase 的踩坑记录

最近在为自己开发的 App 寻找数据统计工具。市面上的选择不少,但大部分要么太重,要么在隐私方面让人不太放心。调研了一圈之后,Aptabase 吸引了我的注意 —— 它主打隐私优先,不使用任何用户唯一标识符,完全符合 GDPR、CCPA 等法规要求,而且自带的 Dashboard 简洁直观,提供了超过 10 种 SDK,基本覆盖了主流的开发框架。更重要的是,它支持 Self-Host,数据完全掌握在自己手里。

官方提供了 Self-Hosting 仓库,看起来很简单 ——clone 下来、改改配置、docker compose up -d 就完事了。但实际部署过程中还是踩了不少坑,Issue 里也有很多人遇到了类似的问题。这里把我的经历整理出来,希望能帮到后来人。

坑一:默认不发邮件,激活链接藏在日志里

Aptabase 默认没有配置 SMTP,注册账号后不会收到任何邮件。但它会把激活链接打印在容器日志里,所以注册完之后需要手动查看日志来获取激活链接:

1
docker logs -f aptabase_app

在日志中找到类似下面的链接,复制到浏览器打开即可激活账号:

1
https://your-domain/api/_auth/continue?token=eyJhbGciOiJIUzI1NiIs...

当然这只是临时方案,后面我们会配置 SMTP 来让邮件正常发送。

坑二:必须配置 HTTPS,否则激活链接会循环跳转

拿到激活链接后点击,却发现页面一直循环跳转回登录页,始终无法完成激活。排查后发现,Aptabase 要求 BASE_URL 必须是 HTTPS,否则认证流程中的 Cookie 和重定向会出问题。

我的服务跑在家里的 Mac Mini 上,没有公网 IP,也不想折腾证书。最终的方案是使用 Cloudflare Tunnel + 自定义域名来解决。

配置 Cloudflare Tunnel

  1. 登录 Cloudflare Dashboard,进入 Zero TrustNetworksTunnels
  2. 点击 Create a tunnel,选择 Cloudflared 类型,给 Tunnel 起一个名字(比如 mac-mini
  3. 按照页面提示,在 Mac Mini 上安装并运行 cloudflared。macOS 上可以用 Homebrew:
1
brew install cloudflared

然后按照页面给出的命令连接 Tunnel(会包含一个 Token):

1
cloudflared service install <your-token>
  1. 在 Tunnel 的 Public Hostname 页面添加一条记录:
    • Subdomainstats(或你喜欢的名字)
    • Domain:选择你在 Cloudflare 上托管的域名,比如 pastepaw.com
    • Servicehttp://localhost:3200(这里的端口对应 docker-compose 中 Nginx 映射的端口)

配置完成后,Cloudflare 会自动管理 HTTPS 证书,外部访问 https://stats.pastepaw.com 的流量会通过 Tunnel 安全地转发到你的 Mac Mini。

坑三:获取不到用户真实 IP

部署完成后发现 Dashboard 里所有用户的 IP 地址都一样 —— 显示的都是 Cloudflare 的 IP,而不是用户的真实 IP。

这是 Cloudflare Tunnel 的经典问题。流量经过 Cloudflare 代理后,到达你的服务时,来源 IP 变成了 Cloudflare 的服务器 IP。真正的用户 IP 被放在了 CF-Connecting-IP 这个 HTTP Header 里,但 Aptabase(基于 ASP.NET Core)默认不会读取这个 Header。

解决方案是在 Aptabase 前面加一层 Nginx,把 CF-Connecting-IP 转换为标准的 X-Forwarded-For,然后让 Cloudflare Tunnel 指向 Nginx 而非直接指向 Aptabase。

Nginx 配置文件 nginx.conf 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
events {
worker_connections 1024;
}

http {
server {
listen 80;

location / {
proxy_pass http://aptabase:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $http_cf_connecting_ip;
proxy_set_header X-Forwarded-For $http_cf_connecting_ip;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}

这样 Aptabase 就能正确识别到用户的真实 IP 了。

坑四:配置 SMTP 邮件发送

日志里翻激活链接终归不是长久之计。Aptabase 支持通过环境变量配置 SMTP,我这里选择了 Resend 作为邮件服务 —— 注册免费,每月有 3,000 封邮件的额度,对个人项目完全够用。

在 Resend 中绑定域名

  1. 注册 Resend 账号
  2. 进入 Domains 页面,点击 Add Domain
  3. 输入你想用于发送邮件的域名(比如 mail.pastepaw.com,也可以和 Aptabase 的域名不同,没有关系)
  4. Resend 会给你几条 DNS 记录需要添加,通常包括:
    • 一条 MX 记录
    • 一条 SPF(TXT)记录
    • 几条 DKIM(TXT)记录
  5. 到你的 DNS 管理面板(比如 Cloudflare)中添加这些记录
  6. 回到 Resend,点击 Verify,等待验证通过(通常几分钟内完成)

生成 API Key

  1. 在 Resend 左侧菜单进入 API Keys 页面
  2. 点击 Create API Key
  3. 给 Key 起个名字(比如 aptabase),权限选择 Sending access,域名限制可以选择刚才绑定的域名
  4. 复制生成的 Key(以 re_ 开头),这就是你的 SMTP 密码

配置环境变量

docker-compose.yml 的 Aptabase 服务中添加以下环境变量:

1
2
3
4
5
SMTP_HOST: smtp.resend.com
SMTP_PORT: 587
SMTP_USERNAME: resend
SMTP_PASSWORD: re_你的API_Key
SMTP_FROM_ADDRESS: [email protected]

注意:这里端口要用 587(STARTTLS),而不是 465(Implicit TLS)。实测使用 465 端口在 Docker 环境下无法正常发送邮件,换成 587 后立刻恢复正常。这也是一个容易踩的坑。

完整的 Docker Compose 配置

以下是最终可用的完整 docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
services:
nginx:
container_name: aptabase_nginx
image: nginx:alpine
restart: always
depends_on:
- aptabase
ports:
- 3200:80
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
mem_limit: 256m
cpus: 0.5
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"

aptabase_db:
container_name: aptabase_db
image: postgres:15-alpine
restart: always
mem_limit: 2g
cpus: 2
volumes:
- ./db-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: aptabase
POSTGRES_PASSWORD: ${PASSWORD}
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"

aptabase_events_db:
container_name: aptabase_events_db
image: clickhouse/clickhouse-server:23.8.4.69-alpine
restart: always
mem_limit: 4g
cpus: 2
volumes:
- ./events-db-data:/var/lib/clickhouse
environment:
CLICKHOUSE_USER: aptabase
CLICKHOUSE_PASSWORD: ${PASSWORD}
ulimits:
nofile:
soft: 262144
hard: 262144
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"

aptabase:
container_name: aptabase_app
image: ghcr.io/aptabase/aptabase:main
restart: always
depends_on:
- aptabase_events_db
- aptabase_db
mem_limit: 2g
cpus: 2
environment:
BASE_URL: https://stats.pastepaw.com
AUTH_SECRET: 替换为你自己的随机密钥
DATABASE_URL: Server=aptabase_db;Port=5432;User Id=aptabase;Password=${PASSWORD};Database=aptabase
CLICKHOUSE_URL: Host=aptabase_events_db;Port=8123;Username=aptabase;Password=${PASSWORD}
SMTP_HOST: smtp.resend.com
SMTP_PORT: 587
SMTP_USERNAME: resend
SMTP_PASSWORD: 替换为你的Resend_API_Key
SMTP_FROM_ADDRESS: [email protected]
logging:
driver: json-file
options:
max-size: 10m
max-file: "3"

需要创建一个 .env 文件来存放数据库密码:

1
echo "PASSWORD=你的数据库强密码" > .env

提醒AUTH_SECRET 务必替换为自己生成的随机字符串,可以在 RandomKeygen 上生成一个。不要使用官方文档里的示例值。

启动服务

1
docker compose up -d

等待所有容器启动完成后,访问 https://stats.pastepaw.com,注册账号,这次你应该能正常收到激活邮件了。

整体架构图

所有组件部署完成后,整体架构如下:

整体架构图

各组件职责:

  • Cloudflare:提供 HTTPS 证书管理和 CDN 加速,通过 Tunnel 将外部流量安全地转发到家里的 Mac Mini
  • Nginx:反向代理,核心作用是将 Cloudflare 注入的 CF-Connecting-IP Header 转换为 X-Forwarded-For,让 Aptabase 能识别用户真实 IP
  • Aptabase:核心应用服务,处理 SDK 上报的事件、管理用户账号、提供 Dashboard
  • PostgreSQL:存储用户账号、应用配置、API Key 等关系型数据
  • ClickHouse:高性能 OLAP 引擎,存储所有上报的事件数据,支撑 Dashboard 的实时分析查询
  • Resend:外部 SMTP 邮件服务,用于发送账号激活邮件等

事件数据流转

当 App 中的 SDK 上报一个事件时,数据会经过以下路径:

事件数据流转

简单来说:SDK 发出的每一个事件,经过 Cloudflare 解密和 IP 标记、Nginx 的 Header 转换、Aptabase 的校验和地理解析后,最终以结构化的形式写入 ClickHouse。而你在 Dashboard 上看到的所有图表和指标,都是从 ClickHouse 中实时查询出来的。

总结

Aptabase 本身是一个非常优秀的轻量级统计工具,但 Self-Host 的文档相对简略,部署过程中有不少需要自己摸索的地方。总结一下关键的几个坑:

  1. 默认不发邮件 —— 激活链接在容器日志里,需要 docker logs -f 查看
  2. 必须 HTTPS—— 否则认证流程会循环跳转,推荐使用 Cloudflare Tunnel 解决
  3. 真实 IP 获取 ——Cloudflare Tunnel 代理后需要加一层 Nginx 做 Header 转换
  4. SMTP 端口 —— 用 587(STARTTLS)而不是 465,后者在 Docker 环境下可能不工作
  5. 安全配置 —— 记得替换默认的 AUTH_SECRET,妥善保管 API Key 和数据库密码

希望这篇文章能帮到同样想 Self-Host Aptabase 的朋友,少走一些弯路。