Aptabase Docker Compose 部署指南

Aptabase 自托管 Docker Compose 部署指南:应用 + PostgreSQL 15 + ClickHouse 三服务编排、首次账号注册与日志激活、SDK 事件上报与面板出数验证、数据持久化与运维命令,基于 main 镜像(2026-10-30)实测。

AptabaseAptabase产品分析2026/10/30

Aptabase Docker Compose 部署指南

目标:在一台 Ubuntu 服务器上用 Docker Compose 从零部署 Aptabase 自托管分析平台(应用 + PostgreSQL + ClickHouse 三服务),完成首个管理员账号注册激活、创建应用、SDK 事件上报与面板出数的全流程验证。已在 Ubuntu 22.04 服务器(Docker 24+)上实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04 LTS(x86_64)
  • Docker:24.x,Docker Compose v2(docker compose 子命令形式)
  • Aptabase 版本:ghcr.io/aptabase/aptabase:main(main 分支滚动镜像,2026-10-30 拉取;Aptabase 不发布语义化版本,见 《Aptabase 介绍与选型指南》第二章)
  • 依赖数据库:PostgreSQL 15(alpine)、ClickHouse 23.8.4.69(alpine,官方编排锁定版本)
  • 应用监听:容器内 8080,宿主机映射 8000(官方编排默认映射为 8000)
  • 服务器 IP:192.168.1.200(下文所有访问地址以它为准,读者按自身服务器 IP 替换)
  • 数据目录:全部落在 Docker 命名卷(见第六章持久化说明)

⚠️ 说明:Aptabase 主仓库无 GitHub Releases,自托管跟随 :main 镜像更新;依赖库版本以官方 self-hosting 仓库编排文件为基准(PostgreSQL 15-alpine 与 ClickHouse 23.8.4.69-alpine 为官方锁定组合),不建议自行大幅升级依赖库版本。

二、镜像选型说明

服务镜像选型依据
应用ghcr.io/aptabase/aptabase:main官方唯一分发渠道(GHCR 官方包),无其他官方 tag
元数据库postgres:15-alpine官方 self-hosting 编排锁定的版本,与 FluentMigrator 迁移脚本兼容
事件库clickhouse/clickhouse-server:23.8.4.69-alpine官方编排锁定版本,建表与查询脚本按该版本验证

选型结论:三个镜像全部来自官方渠道,直接照抄官方组合即可。Aptabase 应用镜像约 600MB,首次拉取时间主要消耗在 GHCR 下载。

三、前置条件

  1. 服务器已安装 Docker 24+ 与 Docker Compose v2,且当前用户在 docker 组;
  2. 端口 8000 未被占用(占用时换成任意空闲端口,同步修改 BASE_URL);
  3. 服务器可访问外网(拉取镜像);GHCR 拉取缓慢时可配置镜像加速或代理;
  4. 浏览器可访问服务器 8000 端口(后续控制台操作用);
  5. 规划好 AUTH_SECRET(随机字符串,用于签发认证令牌,可用 openssl rand -hex 24 生成)。

四、部署步骤

4.1 创建部署目录与编排文件

在服务器上创建部署目录,写入 docker-compose.yml:

mkdir -p /opt/aptabase && cd /opt/aptabase   # 部署目录按惯例放 /opt 下
vim docker-compose.yml                        # 写入下方完整内容

docker-compose.yml 完整内容(基于官方 self-hosting 编排,按本机端口/密码调整):

name: aptabase-main-stack

services:
  aptabase_db:
    container_name: aptabase-main-db
    image: postgres:15-alpine
    restart: always
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: aptabase
      POSTGRES_PASSWORD: q1w2e3r4

  aptabase_events_db:
    container_name: aptabase-main-events-db
    image: clickhouse/clickhouse-server:23.8.4.69-alpine
    restart: always
    volumes:
      - events-db-data:/var/lib/clickhouse
    environment:
      CLICKHOUSE_USER: aptabase
      CLICKHOUSE_PASSWORD: q1w2e3r4
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

  aptabase:
    container_name: aptabase-main-app
    image: ghcr.io/aptabase/aptabase:main
    restart: always
    depends_on:
      - aptabase_events_db
      - aptabase_db
    ports:
      - "8000:8080"
    environment:
      BASE_URL: http://192.168.1.200:8000
      AUTH_SECRET: c4rI4x8kz5DgKJ1is5Eiu9bNncSQ6ROD
      DATABASE_URL: Server=aptabase_db;Port=5432;User Id=aptabase;Password=q1w2e3r4;Database=aptabase
      CLICKHOUSE_URL: Host=aptabase_events_db;Port=8123;Username=aptabase;Password=q1w2e3r4

networks:
  aptabase-net:
    driver: bridge
    name: aptabase-main-net

volumes:
  db-data:
    name: aptabase-main-db-data
    driver: local
  events-db-data:
    name: aptabase-main-events-data
    driver: local

逐项说明:

  • name: aptabase-main-stack:显式声明 Compose 项目名,避免多套部署互相干扰(down 按项目名隔离);
  • aptabase_db(PostgreSQL):存用户/应用/分享/功能开关等元数据,POSTGRES_USER/PASSWORD 与应用侧 DATABASE_URL 中的凭据必须一致;
  • aptabase_events_db(ClickHouse):存事件明细与会话数据;ulimits.nofile 调到 262144 是官方编排自带配置,防止高并发下文件句柄不足;
  • aptabase(应用):BASE_URL 是全站链接与激活邮件里生成地址的基准,必须是浏览器可达的「协议 + IP/域名 + 端口」完整地址;DATABASE_URL/CLICKHOUSE_URL 使用 .NET 连接串格式,主机名直接写服务名(同一 Compose 网络内可解析);
  • 顶层 volumes:两个命名卷承接两个库的数据落盘(见第六章)。

⚠️ 注意:官方示例中的 AUTH_SECRET 与数据库口令都是占位样例,实际部署务必替换;BASE_URL 若写成浏览器访问不到的地址,激活链接将无法打开。

4.2 启动服务

docker compose up -d

实测输出(首次运行包含镜像拉取,节选):

[+] Running 8/8
 ✔ Network aptabase-main-net         Created
 ✔ Volume "aptabase-main-db-data"   Created
 ✔ Volume "aptabase-main-events-data" Created
 ✔ Container aptabase-main-db        Started
 ✔ Container aptabase-main-events-db Started
 ✔ Container aptabase-main-app       Started

4.3 确认服务状态

docker compose ps

实测输出:

NAME                      IMAGE                                           STATUS          PORTS
aptabase-main-app         ghcr.io/aptabase/aptabase:main                  Up 29 seconds   0.0.0.0:8000->8080/tcp, [::]:8000->8080/tcp
aptabase-main-db          postgres:15-alpine                              Up 30 seconds   5432/tcp
aptabase-main-events-db   clickhouse/clickhouse-server:23.8.4.69-alpine   Up 30 seconds   8123/tcp, 9000/tcp, 9009/tcp

三个服务均为 Up 状态,应用端口 8000 已映射。应用启动时会自动对 PostgreSQL 执行结构迁移(FluentMigrator),确认迁移完成后 Web 才可用:

docker compose logs aptabase 2>&1 | tail -n 4

实测输出(节选):

{"EventId":14,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Now listening on: http://[::]:8080",...}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Application started. Press Ctrl+C to shut down.",...}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Hosting environment: Production",...}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Content root path: /app",...}

看到 Now listening on: http://[::]:8080 即应用就绪。最后做一次 HTTP 探活:

curl -s -o /dev/null -w "HTTP %{http_code}\n" http://192.168.1.200:8000/

实测输出:

HTTP 200

4.4 注册首个管理员账号

Aptabase 没有预置账号,首次使用先注册(浏览器打开 http://192.168.1.200:8000/register,或用接口注册)。用接口注册的实测过程:

curl -s -X POST http://192.168.1.200:8000/api/_auth/register \
  -H 'Content-Type: application/json' \
  -d '{"name":"admin","email":"admin@aptabase.local","password":"q1w2e3r4"}' \
  -w "\nHTTP %{http_code}\n"

实测输出:

{}
HTTP 200

4.5 从日志取激活链接并激活

自托管默认不配 SMTP,注册/登录邮件直接写入应用日志。从日志中提取激活链接:

docker compose logs aptabase 2>&1 | grep -oE 'http://192.168.1.200:8000/api/_auth/continue\?token=[A-Za-z0-9_.-]+' | tail -n 1

实测输出(token 为 JWT,节选展示):

http://192.168.1.200:8000/api/_auth/continue?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiUmVnaXN0ZXIiLCJuYW1lIjoiYWRtaW4iLCJlbWFpbCI6ImFkbWluQGFwdGFiYXNlLmxvY2FsIiwibmJmIjoxNzkzMzI3MjMzLCJleHAiOjE3OTMzMjgxMzMsImlhdCI6MTc5MzMyNzIzMywiaXNzIjoiYXB0YWJhc2Utc2gifQ.ONtbyzf64iyXK7GdfJ0MY_oDMC2mxb1NLYaVi8V6g4o

浏览器打开该链接完成激活(实测返回 302 跳转到控制台)。token 有效期 15 分钟(JWT 中 nbf 到 exp 相差 900 秒),过期则重新发起注册/登录。

⚠️ 生产环境建议配置 SMTP(见第七章环境变量表),让激活/登录邮件走真实邮箱,而不是人人都能翻日志。

4.6 登录与访问控制台

登录入口 http://192.168.1.200:8000/login,输入邮箱提交后,Aptabase 走「魔法链接」登录——服务端向邮箱发送登录链接(未配 SMTP 时同样落在日志里),打开该链接即建立会话:

# 发起登录:只传 email,不需要密码
curl -s -X POST http://192.168.1.200:8000/api/_auth/signin \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@aptabase.local"}' -w "\nHTTP %{http_code}\n"

实测输出:

{}
HTTP 200

再从日志取 Signin 类型的 continue 链接打开即可登录。验证会话(用浏览器登录后可跳过):

curl -s http://192.168.1.200:8000/api/_auth/me -H "Cookie: <登录后浏览器中的 auth-session Cookie>"

实测输出:

{"id":"LFHJvK4ViazBQ0Aw9vCUGj","name":"admin","email":"admin@aptabase.local","avatarUrl":"https://aptabase.com/avatar/f9192f0b63fe0160a769ed571fd7be78?s=80&d=404","lockReason":null}

⚠️ 排障实录(重要):认证 Cookie auth-session 携带 Secure 属性,纯 HTTP 访问时浏览器/命令行无法回传该 Cookie,典型现象是「登录接口返回 200、点击日志里的登录链接也 302,但回到控制台仍是未登录状态」。这是 BASE_URL 使用 http:// 时的固有限制,生产部署必须前置 Nginx/Caddy 等 TLS 反代并以 https:// 作为 BASE_URL。本篇实验环境为验证全流程,直接操作 Cookie 头完成调用;读者照做时若在 HTTP 下遇登录失效,属预期现象而非故障(见第十一章常见问题第 1 条)。

五、部署验证:建应用、上报事件、面板出数

5.1 创建应用拿到 App Key

登录后创建一个应用(控制台「Create App」,或接口方式):

curl -s -X POST http://192.168.1.200:8000/api/_apps \
  -H 'Content-Type: application/json' \
  -H "Cookie: <登录会话 Cookie>" \
  -d '{"name":"Demo Mobile App"}' -w "\nHTTP %{http_code}\n"

实测输出:

{"id":"n85jSLY53PVjU3H7GBXZyU","name":"Demo Mobile App","appKey":"A-SH-4878303621","iconPath":"","hasEvents":false,"hasOwnership":false,"lockReason":null}
HTTP 200

appKey(本例 A-SH-4878303621,A-SH 前缀代表 Self-Hosted 实例)是客户端上报事件的唯一凭据。

5.2 上报测试事件

模拟客户端 SDK 上报一条事件:

curl -s -X POST http://192.168.1.200:8000/api/v0/event \
  -H 'App-Key: A-SH-4878303621' \
  -H 'Content-Type: application/json' \
  -d '{"eventName":"screen_view","sessionId":"3fa85f64-5717-4562-b3fc-2c963f66afa6","props":{"screen":"Home"},"timestamp":"2026-10-30T02:35:00Z","systemProps":{"osName":"Android","osVersion":"14","sdkVersion":"aptabase-js@1.2.0","locale":"zh-CN","appVersion":"1.0.0"}}' \
  -w "\nHTTP %{http_code}\n"

实测输出:

{}
HTTP 200

⚠️ 若 App-Key 传错,接口仍返回 HTTP 200,但响应体是明确的错误信息:{"error":"Application not found with given app key: A-SH-xxxx. Events are not being recorded."}——接入 SDK 后看不到数据时先检查响应体而不是状态码。

再连续上报 16 条不同事件/平台的数据(命令模式同上,替换 eventName、sessionId 与 systemProps.osName),实测 16 条全部返回 HTTP 200。事件上报协议的字段说明与逐项验证见 《Aptabase API 使用详解》。

5.3 验证面板出数

等待约 1 分钟(应用后台批量把缓冲事件刷入 ClickHouse 后面板才可见),查询核心指标:

curl -s "http://192.168.1.200:8000/api/_stats/metrics?appId=n85jSLY53PVjU3H7GBXZyU&periodStart=2026-10-01&periodEnd=2026-10-30" \
  -H "Cookie: <登录会话 Cookie>"

实测输出:

{"current":{"dailyUsers":2,"sessions":9,"events":17,"durationSeconds":0},"previous":null}

17 条事件、9 个会话、2 个活跃用户,与上报数据一致;浏览器打开控制台首页同样能看到曲线与指标。再验证事件 Top 榜:

curl -s "http://192.168.1.200:8000/api/_stats/top-events?appId=n85jSLY53PVjU3H7GBXZyU&periodStart=2026-10-01&periodEnd=2026-10-30" \
  -H "Cookie: <登录会话 Cookie>"

实测输出:

[{"name":"screen_view","value":9},{"name":"button_click","value":8}]

至此「部署 → 注册 → 激活 → 登录 → 建应用 → 上报 → 出数」全链路闭环,部署完成。

六、数据持久化说明

卷名容器内路径存放内容
aptabase-main-db-data/var/lib/postgresql/dataPostgreSQL 全部元数据(用户/应用/权限/功能开关等)
aptabase-main-events-data/var/lib/clickhouseClickHouse 事件明细、会话与聚合表

两个卷均为命名卷,docker compose down 不会删除;docker compose down -v 会连同卷一起删除(彻底重置时使用,数据不可恢复)。备份时两个卷都要备(元数据与事件数据分离存储,缺一不可),可直接用 docker run --rm -v aptabase-main-db-data:/data -v /backup:/backup ubuntu tar czf /backup/db.tgz -C /data . 形式导出,或用数据库原生工具(pg_dump/clickhouse-backup)。

验证持久化:down 后重新 up,此前注册的账号与应用仍在(元数据在卷里),事件曲线不丢(事件数据在卷里)。

七、环境变量表

Aptabase 应用自身无配置文件,全部行为由环境变量驱动(完整定义见仓库 src/Features/EnvSettings.cs,逐项详解见配置专题篇)。Compose 编排涉及的环境变量如下:

变量所属服务本篇取值说明
POSTGRES_USERaptabase_dbaptabasePostgreSQL 用户名,与应用 DATABASE_URL 中 User Id 一致
POSTGRES_PASSWORDaptabase_dbq1w2e3r4PostgreSQL 口令,与 DATABASE_URL 中 Password 一致
CLICKHOUSE_USERaptabase_events_dbaptabaseClickHouse 用户名,与 CLICKHOUSE_URL 中 Username 一致
CLICKHOUSE_PASSWORDaptabase_events_dbq1w2e3r4ClickHouse 口令,与 CLICKHOUSE_URL 中 Password 一致
BASE_URLaptabasehttp://192.168.1.200:8000站点基准地址(生成激活/登录链接用),生产应为 https://
AUTH_SECRETaptabase随机字符串(实测样例)认证令牌签名密钥,泄露可伪造登录链接,必须替换
DATABASE_URLaptabaseServer=aptabase_db;Port=5432;User Id=aptabase;Password=q1w2e3r4;Database=aptabasePostgreSQL 连接串(.NET 格式)
CLICKHOUSE_URLaptabaseHost=aptabase_events_db;Port=8123;Username=aptabase;Password=q1w2e3r4ClickHouse 连接串(.NET 格式,8123 为 HTTP 端口)

生产环境通常还需要追加 SMTP 与 OAuth 配置(SMTP_HOST/SMTP_PORT/SMTP_USERNAME/SMTP_PASSWORD/SMTP_FROM_ADDRESS、OAUTH_GITHUB_CLIENT_ID 等),完整清单与行为见配置专题篇。

八、配置参数说明

  • 端口:应用容器内固定监听 8080(非 root 端口),对外映射端口由 ports 决定,可换任意空闲端口;ClickHouse 8123(HTTP)/9000(Native)仅在 Compose 内网使用,无需对外映射;
  • BASE_URL 规范:必须是绝对地址;不带协议时服务端自动补 https://(源码 NormalizeBaseUrl 逻辑),因此内网 HTTP 部署必须显式写全 http://IP:端口;
  • 迁移行为:应用每次启动自动执行数据库迁移(日志 Category 为 FluentMigrator.Runner.MigrationRunner),升级镜像后首次启动会看到一批 migrating/migrated 日志,属正常流程;
  • 依赖就绪:depends_on 只保证启动顺序不保证服务可用;PostgreSQL/ClickHouse 秒级就绪,应用自带重试,无需额外健康检查脚本。

九、配置文件挂载

Aptabase 无需挂载任何配置文件:全部配置走环境变量(这是它与多数自托管软件的差异点,省去配置文件版本管理)。两个数据库各自的数据目录由命名卷接管,同样无需宿主机目录挂载。唯一可能的自定义挂载场景是为应用挂 TLS 证书——官方镜像不内置 TLS 终结,推荐由前置反向代理(Nginx/Caddy)承担,Aptabase 只走内网 HTTP。

十、等价 docker run

等价于上文编排的单命令启动方式(逐条执行,按依赖顺序;仅用于理解编排结构,生产建议仍用 Compose):

# 1. 创建内网
docker network create aptabase-main-net

# 2. 启动 PostgreSQL(元数据)
docker run -d --name aptabase-main-db \
  --network aptabase-main-net --restart always \
  -e POSTGRES_USER=aptabase -e POSTGRES_PASSWORD=q1w2e3r4 \
  -v aptabase-main-db-data:/var/lib/postgresql/data \
  postgres:15-alpine

# 3. 启动 ClickHouse(事件库)
docker run -d --name aptabase-main-events-db \
  --network aptabase-main-net --restart always \
  --ulimits nofile 262144:262144 \
  -e CLICKHOUSE_USER=aptabase -e CLICKHOUSE_PASSWORD=q1w2e3r4 \
  -v aptabase-main-events-data:/var/lib/clickhouse \
  clickhouse/clickhouse-server:23.8.4.69-alpine

# 4. 启动 Aptabase 应用
docker run -d --name aptabase-main-app \
  --network aptabase-main-net --restart always \
  -p 8000:8080 \
  -e BASE_URL=http://192.168.1.200:8000 \
  -e AUTH_SECRET=c4rI4x8kz5DgKJ1is5Eiu9bNncSQ6ROD \
  -e "DATABASE_URL=Server=aptabase_db;Port=5432;User Id=aptabase;Password=q1w2e3r4;Database=aptabase" \
  -e "CLICKHOUSE_URL=Host=aptabase_events_db;Port=8123;Username=aptabase;Password=q1w2e3r4" \
  ghcr.io/aptabase/aptabase:main

十一、常用运维命令

均在部署目录(docker-compose.yml 所在目录)执行:

docker compose ps                 # 查看三服务状态与端口映射
docker compose logs -f aptabase   # 跟踪应用日志(含激活/登录链接与迁移过程)
docker compose restart aptabase   # 仅重启应用(改环境变量后必须重建而非 restart)
docker compose pull               # 拉取 main 镜像最新版(升级第一步)
docker compose up -d              # 应用新镜像/新环境变量(自动重建有变化的容器)
docker compose down               # 停止并移除容器与网络(保留数据卷)
docker compose down -v            # 彻底重置:连数据卷一起删除(危险,数据不可恢复)

升级流程(main 滚动模式的标准操作):先 docker compose pull,再 docker compose up -d,随后 docker compose logs aptabase 观察迁移日志走完、curl 探活 200。升级前建议按第六章做两个数据卷的备份。

十二、常见问题

Q1:登录成功但刷新后又是未登录状态? 认证 Cookie 带 Secure 属性,BASE_URL 是 http:// 时浏览器不会在后续请求中回传。生产环境用 Nginx/Caddy 终结 TLS,BASE_URL 改为 https://域名;内网无 TLS 的实验环境需接受该限制,或用接口方式携带 Cookie 头操作。

Q2:上报返回 200 但面板没有数据? 两处检查:一是看响应体——App-Key 错误时 200 也返回 {"error":"Application not found with given app key: ..."};二是事件从接收到面板可见有约 1 分钟延迟(应用内缓冲批量刷入 ClickHouse),刚上报完立即查询为 0 属预期,等待后复查。

Q3:注册/登录链接打开提示过期? continue 链接是 15 分钟有效期的 JWT。重新执行一次 signin(或重新注册),从日志取最新链接即可。

Q4:应用启动失败,日志提示 Missing 环境变量? BASE_URL、AUTH_SECRET、DATABASE_URL 是必填项(源码 MustGet 逻辑),漏配任意一个启动即抛 Missing <变量名> environment variable。核对第七章环境变量表补齐后 docker compose up -d 重建。

Q5:ClickHouse 启动报文件句柄不足? 确认编排保留了 ulimits.nofile 262144 配置(官方默认自带);被删掉后高负载下可能触发句柄耗尽。

Q6:想固定版本不跟随 main 滚动怎么办? 镜像按 digest 固定:docker images --digests ghcr.io/aptabase/aptabase 查到当前 digest,编排中写 ghcr.io/aptabase/aptabase:main@sha256:<digest>。升级时手动更新 digest 并按第十一章流程验证。

十三、参考链接

  1. 官方自托管指南(编排文件来源):https://github.com/aptabase/self-hosting
  2. 主仓库与环境变量定义:https://github.com/aptabase/aptabase
  3. 环境变量全量定义源码:https://github.com/aptabase/aptabase/blob/main/src/Features/EnvSettings.cs
  4. 部署与选型背景:《Aptabase 介绍与选型指南》
  5. 环境变量逐项详解:《Aptabase 配置参数详解》
  6. 事件上报与面板接口逐条实测:《Aptabase API 使用详解》