OpenHabitTracker Docker Compose 部署指南

OpenHabitTracker 1.2.5 官方镜像 Docker Compose 部署实战:镜像选型、环境变量、数据持久化(SQLite+DataProtection)、备份升级回滚与口令坑位,全部步骤在 Ubuntu 22.04 实测通过。

OpenHabitTracker习惯追踪自托管2026/10/24

OpenHabitTracker Docker Compose 部署指南

目标:在一台 Linux 服务器上用 Docker Compose 把 OpenHabitTracker 1.2.5 自托管起来,完成「部署 → 登录 → 验证 → 数据持久化 → 升级与备份」的完整闭环。以下所有命令与回显均在 Ubuntu 22.04(x86_64)+ Docker 27 环境、OpenHabitTracker 1.2.5 镜像下实测通过,宿主机服务端口以 8129 为例(可自行替换为未被占用的端口)。

一、环境信息

  • 操作系统:Ubuntu 22.04(x86_64),内核 5.15+
  • Docker:27.x(Docker Engine 自带 Compose v2 子命令,docker compose)
  • 应用版本:OpenHabitTracker 1.2.5(官方镜像 jinjinov/openhabittracker:1.2.5,镜像内为 .NET 10.0.12 运行时)
  • 部署形态:单容器 Blazor Server 应用,监听容器内 8080 端口,宿主机映射 8129
  • 数据存储:SQLite 单文件 OpenHT.db + ASP.NET Core DataProtection 密钥文件,统一落在挂载目录 .OpenHabitTracker/ 下
  • 官方镜像发布渠道:Docker Hub jinjinov/openhabittracker 与 GHCR ghcr.io/jinjinov/openhabittracker(README 2026-10-24 查询)
  • 实验目录约定:本文所有文件都放在服务器的部署目录(示例用 /opt/openhabittracker,按你的目录习惯替换即可)

二、镜像选型说明

官方维护两个发布渠道,tag 策略一致。以下为 Docker Hub 实查的 tag 列表(2026-10-24 查询,共 16 个 tag,节选):

Tag构建日期压缩体积说明
latest2026-09-27107 MB跟随最新稳定版,当前指向 1.2.5
1.2.52026-09-27107 MB当前最新稳定版
1.2.42026-08-13110 MB
1.2.32026-07-10110 MB
1.2.22026-05-25132 MB
1.1.82026-03-28132 MB
1.0.02025-01-27116 MB首个正式版

选型建议:

  • 生产固定版本 tag(如 1.2.5):升级完全由你控制,避免 latest 意外滚动导致重启后版本漂移;
  • 个人尝鲜用 latest:拉取即最新,但要在 README 的 Release Notes 里跟进行为变化;
  • 单用户设计(官方 README 明确 "Docker image contains a Blazor Server app for one user"),不需要挂载数据库外置服务,一个容器即全部;
  • 镜像内监听 8080(ASPNETCORE_HTTP_PORTS=8080,入口进程 dotnet OpenHT.dll,工作目录 /app,均可由 docker inspect 复核),无需自行配置 Kestrel 监听地址。

三、前置条件

  1. 服务器已安装 Docker Engine 24+(含 Compose v2):
docker --version && docker compose version

实测回显:

Docker version 27.3.1, build 942c34b
Docker Compose version v2.39.2
  1. 规划一个未被占用的宿主机端口(本文以 8129 为例):
ss -ltn | grep 8129 || echo "port free"

实测回显(无输出即空闲):

port free
  1. 生成 JWT 签名密钥(应用用它签发/校验登录令牌,必须换成强随机值):
openssl rand -base64 32

实测回显(每次生成都不同,取你自己的值):

BI/pnDHJsnvA4kDltCsULSGvXuyFe5HfFhVJhB7y8OE=
  1. 规划初始账号:用户名、邮箱、口令。口令无复杂度要求(应用侧 Identity 校验已放宽),但对外服务请务必用强口令。

⚠️ 注意:本文演示口径统一用账号 admin / 口令 q1w2e3r4,生产环境请替换为强口令。

四、部署步骤

4.1 建立部署目录与 .env

在服务器上创建部署目录,写入 .env 存放敏感配置(与 compose 文件分离):

mkdir -p /opt/openhabittracker/data/.OpenHabitTracker
cd /opt/openhabittracker
cat > .env <<'EOF'
APPSETTINGS_USERNAME=admin
APPSETTINGS_EMAIL=admin@admin.com
APPSETTINGS_PASSWORD=q1w2e3r4
APPSETTINGS_JWT_SECRET=BI/pnDHJsnvA4kDltCsULSGvXuyFe5HfFhVJhB7y8OE=
EOF

.env 四个变量逐项说明:

变量作用示例值
APPSETTINGS_USERNAME初始登录用户名,同时是 watchdog 日志页账号admin
APPSETTINGS_EMAIL初始账号邮箱(应用内展示用)admin@admin.com
APPSETTINGS_PASSWORD初始账号口令,同上也是 watchdog 口令q1w2e3r4
APPSETTINGS_JWT_SECRETJWT 签名密钥,泄露可伪造登录令牌openssl rand -base64 32 产物

4.2 编写 docker-compose.yml

在同目录创建 docker-compose.yml(显式声明项目名,避免多套部署同名互扰):

name: oht-1.2.5-stack
services:
  openhabittracker:
    image: jinjinov/openhabittracker:1.2.5
    container_name: oht-1.2.5-server
    ports:
      - "8129:8080"
    environment:
      - AppSettings__UserName=${APPSETTINGS_USERNAME}
      - AppSettings__Email=${APPSETTINGS_EMAIL}
      - AppSettings__Password=${APPSETTINGS_PASSWORD}
      - AppSettings__JwtSecret=${APPSETTINGS_JWT_SECRET}
      - TZ=Asia/Shanghai
    volumes:
      - ./data/.OpenHabitTracker:/app/.OpenHabitTracker
    restart: unless-stopped

逐项说明:

  • ports: "8129:8080":宿主机 8129 → 容器内 8080(应用固定监听 8080);官方示例用 5050,任何空闲端口都可;
  • AppSettings__*:双下划线是 ASP.NET Core 环境变量覆盖配置层级的标准写法,对应 appsettings.json 的 AppSettings 节;
  • TZ:容器内时区,影响「今天」的判定与统计口径,务必设为你的 IANA 时区(如 Asia/Shanghai);
  • volumes:把应用数据目录挂到宿主机,备份/迁移只需要拷这一个目录;
  • restart: unless-stopped:服务器重启后自动拉起(官方示例未带,生产建议加上)。

4.3 启动

docker compose up -d

实测回显:

 Container oht-1.2.5-server  Started

⚠️ 国内网络拉取 Docker Hub 超时时:为 Docker 守护进程配置镜像加速器,或通过 HTTP 代理拉取(docker pull 支持走代理的 daemon 配置),后再执行 docker compose up -d。

4.4 启动验证

看容器状态:

docker ps --filter name=oht-1.2.5-server --format '{{.Names}} {{.Status}} {{.Ports}}'

实测回显:

oht-1.2.5-server Up 12 seconds 0.0.0.0:8129->8080/tcp, [::]:8129->8080/tcp

看应用启动日志(首次启动会自动建库、跑迁移、创建初始用户):

docker logs oht-1.2.5-server 2>&1 | grep -E 'Now listening|Initializing|already up to date|Default user' | head -5

实测回显(节选,按日志行匹配):

info: Program[0]
      Initializing database
      No migrations were applied. The database is already up to date.
Default user created successfully.
      Now listening on: http://[::]:8080
      Application started. Press Ctrl+C to shut down.

HTTP 层验证(首页会 302 跳登录页,登录页本身 200):

curl -s -o /dev/null -w 'GET /: %{http_code} -> %{redirect_url}\n' http://localhost:8129/
curl -s -o /dev/null -w 'GET /login: %{http_code}\n' http://localhost:8129/login

实测回显:

GET /: 302 -> http://localhost:8129/login
GET /login: 200

浏览器打开 http://<服务器IP>:8129/login,用 .env 中的用户名口令登录即进入主界面。登录后同一会话内还可以访问:

  • 日志查看页:http://<服务器IP>:8129/watchdog(账号口令与登录账号一致)
  • OpenAPI UI(Scalar):http://<服务器IP>:8129/scalar/v1
  • OpenAPI JSON:http://<服务器IP>:8129/openapi/v1.json

⚠️ 注意:/scalar/v1 与 /openapi/v1.json 需要浏览器登录态(Cookie)才能访问;用 JWT 令牌直接访问这两个路径会得到 401——这两个入口走的是浏览器 Cookie 认证策略,而数据接口(/api/*)走 JWT Bearer 认证,实测行为见本专栏《OpenHabitTracker 1.2.5 API 使用详解》。

4.5 客户端接入同步

桌面/手机客户端(或官方 PWA)开启同步:应用内菜单 → Data → Online sync → 填服务器地址 http://<服务器IP>:8129、用户名与口令 → 勾选 Remember me → Log in(官方 README 步骤,2026-10-24 查询)。

五、数据持久化说明

应用数据全部集中在挂载目录一个文件夹里(宿主机 ./data/.OpenHabitTracker/,容器内 /app/.OpenHabitTracker/)。首次启动后实查内容:

ls -la /opt/openhabittracker/data/.OpenHabitTracker/

实测回显:

total 152
drwxr-xr-x 2 root root   4096 Oct 24 12:17 .
drwxr-xr-x 3 root root   4096 Oct 24 12:17 ..
-rw------- 1 root root   1001 Oct 24 12:17 key-a0f0a8f2-ebd0-4156-9f2d-aa9d84ca1b43.xml
-rw-r--r-- 1 root root 143360 Oct 24 12:17 OpenHT.db

两个文件的角色:

文件作用丢失后果
OpenHT.dbSQLite 数据库:习惯/任务/笔记/分类/优先级/设置 + Identity 用户表 + 刷新令牌表数据全丢
key-*.xmlASP.NET Core DataProtection 密钥(加密登录 Cookie)Cookie 失效,浏览器端需重新登录(数据无损)

持久化实测:创建一条习惯 → 重启容器 → 数据仍在。

# 重启
docker compose restart

实测回显:

 Container oht-1.2.5-server  Started

重启后重新登录并读取习惯列表,实测回显:

habits after restart: ['持久化验证习惯']

同时 key-*.xml 文件在重启前后保持不变,浏览器登录态不失效。

六、环境变量表

容器可识别的全部环境变量(AppSettings__* 对应配置节,其余为运行时变量):

环境变量默认值(appsettings.json)说明
AppSettings__UserNameadmin初始账号用户名;仅首次建库生效(见第六章下方 ⚠️)
AppSettings__Emailadmin@admin.com初始账号邮箱;仅首次建库生效
AppSettings__Passwordadmin初始账号口令;仅首次建库生效(见第六章下方 ⚠️)
AppSettings__JwtSecretyour-extremely-strong-secret-keyJWT 签名密钥;每次变更会使已签发令牌全部失效
TZEtc/UTC容器时区,影响“今天”判定、统计与通知时间
ASPNETCORE_HTTP_PORTS8080容器内监听端口(官方镜像默认,一般不改)
Logging__LogLevel__DefaultInformation日志级别,如调认证细节可设 Logging__LogLevel__Microsoft.AspNetCore.Authentication=Debug
ASPNETCORE_ENVIRONMENTProduction运行环境标识,生产保持默认

⚠️ 重要实测结论:APPSETTINGS_PASSWORD(及用户名/邮箱)只在数据库里还没有同名用户时生效——应用启动逻辑为「用户不存在 → 按环境变量创建」。实测把 APPSETTINGS_PASSWORD 改成新口令并重启后,旧口令登录返回 200、新口令登录返回 401。也就是说:改口令环境变量不会修改已存在账号的口令。改回环境变量并重启后旧口令恢复正常。

七、配置参数说明

三层配置(优先级从高到低):

  1. 环境变量(AppSettings__Key):容器部署首选,改完 docker compose up -d 重建生效;
  2. appsettings.json(容器内 /app/appsettings.json):官方镜像内置,默认值见上表;如需整文件覆盖可挂载 -v ./appsettings.json:/app/appsettings.json:ro;
  3. 应用内设置:登录后在 Settings 页调整(主题、语言、界面显隐等),保存在 SQLite 的 settings 表并随 JSON/YAML 备份导出,优先级高于前两者(只影响应用行为,不影响账号与密钥)。

配置生效的验证方法:环境变量改动后看启动日志是否正常监听、再实测登录(账号类变更的生效与坑位见第六章 ⚠️ 与第十章常见问题)。

八、配置文件挂载

一般场景只需要挂载数据目录(第四章 compose 已包含)。需要定制时追加挂载:

    volumes:
      - ./data/.OpenHabitTracker:/app/.OpenHabitTracker
      # 可选:覆盖应用配置文件(不推荐,环境变量方式更直观)
      # - ./appsettings.json:/app/appsettings.json:ro

挂载属主:容器以 root 运行(docker inspect 显示未设 user),宿主机目录权限保持默认 root 属主即可;若以非 root 用户部署,chown 成容器内运行用户(默认 root)或显式加 user: 并同步目录属主。

九、等价 docker run

不想用 Compose 时,等价的一条 docker run(参数与第四章 compose 完全一致):

docker run -d \
  --name oht-1.2.5-server \
  -p 8129:8080 \
  -e AppSettings__UserName=admin \
  -e AppSettings__Email=admin@admin.com \
  -e AppSettings__Password=q1w2e3r4 \
  -e AppSettings__JwtSecret=BI/pnDHJsnvA4kDltCsULSGvXuyFe5HfFhVJhB7y8OE= \
  -e TZ=Asia/Shanghai \
  -v /opt/openhabittracker/data/.OpenHabitTracker:/app/.OpenHabitTracker \
  --restart unless-stopped \
  jinjinov/openhabittracker:1.2.5

启动后验证方式与 4.4 相同。生产建议仍用 Compose:配置入文件、便于版本化与升级重建。

十、常用运维命令

以下命令均在部署目录(含 docker-compose.yml)内执行。

# 查看运行状态
docker compose ps

实测回显:

NAME               IMAGE                             COMMAND                  SERVICE            CREATED         STATUS         PORTS
oht-1.2.5-server   jinjinov/openhabittracker:1.2.5   "dotnet OpenHT.dll"      openhabittracker   2 minutes ago   Up 2 minutes   0.0.0.0:8129->8080/tcp, [::]:8129->8080/tcp
# 跟踪日志
docker compose logs -f --tail 50

实测回显(节选):

oht-1.2.5-server  |       Now listening on: http://[::]:8080
oht-1.2.5-server  |       Application started. Press Ctrl+C to shut down.
# 重启(数据保留)
docker compose restart

实测回显:

 Container oht-1.2.5-server  Starting
 Container oht-1.2.5-server  Started
# 停止(数据保留,一并移除网络)
docker compose down

实测回显:

[+] Running 3/3
 ✔ Container oht-1.2.5-server  Removed
 ✔ Network oht-1.2.5-net       Removing
 ✔ Network oht-1.2.5-net       Removed

备份(停机或运行中均可,SQLite 单文件冷拷贝最稳):

# 运行中在线备份(推荐先停写:down 再备份最稳)
tar -czf oht-backup-$(date +%F).tar.gz -C data .OpenHabitTracker

实测回显(无输出即成功,产物在当前目录):

# ls -lh oht-backup-*.tar.gz
-rw-r--r-- 1 root root 6.9K Oct 24 12:36 oht-backup-2026-10-24.tar.gz

恢复:解压回 data/.OpenHabitTracker/ 后 docker compose up -d。

升级(固定 tag 场景:改 compose 中版本号 → 重建):

sed -i 's|image: jinjinov/openhabittracker:1.2.4|image: jinjinov/openhabittracker:1.2.5|' docker-compose.yml
docker compose up -d

实测回显(tag 从 latest 切到 1.2.5 的重建过程):

 Container oht-1.2.5-server  Starting
 Container oht-1.2.5-server  Started

升级后数据实测保留(重启后读取习惯列表):

habits after tag switch: ['持久化验证习惯']

回滚:把 compose 里的 tag 改回旧版本号再 docker compose up -d(数据库迁移由应用启动时自动执行,回滚跨多个版本前先用备份验证)。

十一、常见问题(FAQ)

Q1:端口想换成 80,怎么改? 把 compose 中 "8129:8080" 改为 "80:8080" 即可(官方 README 同样建议:默认 5050 可换成 80 或任何空闲端口)。宿主机 80 被占用时用 ss -ltnp | grep :80 找到占用进程处理。

Q2:改了 APPSETTINGS_PASSWORD 为什么新口令登不进? 初始账号只按环境变量创建一次(用户已存在时启动逻辑跳过创建)。实测旧口令 200、新口令 401。要更换口令:在应用 Web 界面内修改(登录后账号页),或接受重建(备份并清空 OpenHT.db 会让所有数据丢失,不推荐)。所以首次部署时就把口令设成最终值。

Q3:拉取镜像很慢或超时? 为 Docker daemon 配置镜像加速器或 HTTP 代理后重试;镜像压缩体积约 107 MB(1.2.5),首次拉取稍慢属正常。

Q4:/openapi/v1.json 用 JWT 访问返回 401? 该路径与 /scalar/v1 由浏览器 Cookie 认证策略保护,JWT 无效——先在浏览器登录 /login,同一会话再访问即可(实测带登录 Cookie 拉取返回完整 OpenAPI 3.1.1 文档,34 个路径)。数据接口 /api/* 才是 JWT Bearer 认证。

Q5:数据能多人共用吗? 官方镜像为单用户设计(一个部署对应一个账号)。多人使用需各自部署一套(不同端口/不同数据目录),或自行改造源码——官方不支持多用户。

Q6:忘记 watchdog 密码? watchdog 页账号口令与 AppSettings__UserName/AppSettings__Password 一致;因初始账号口令以数据库哈希为准(见 Q2),watchdog 口令同样以首次创建时的口令为准,与后来环境变量里的值无关。

Q7:升级后统计图表异常? 1.2.5 新增了习惯统计图表;如从旧版本升级后图表缺失,先强刷浏览器(Ctrl+F5)排除 PWA 静态资源缓存,再确认版本 tag 已切换(docker inspect <容器名> --format '{{.Config.Image}}')。

十二、参考链接