Umami Docker Compose 部署指南
在 Ubuntu 22.04 上用 Docker Compose 从零部署 Umami 3.4.0 + PostgreSQL 16:镜像选型、编排文件逐项讲解、初始化验证、升级备份与常见问题排障,全程实测回显。
Umami Docker Compose 部署指南
目标:在一台 Ubuntu 22.04 服务器上用 Docker Compose 从 0 到 1 部署 Umami 3.4.0 网站统计服务(PostgreSQL 16 存储),完成镜像选型、编排文件、启动验证、首登改密、建站嵌码、数据持久化、备份与升级;全部命令与回显均在 Ubuntu 22.04.4 LTS(x86_64)+ Umami 3.4.0 + PostgreSQL 16.15 实测通过。
一、环境信息
本文所有命令与回显的实测环境:
- 操作系统:Ubuntu 22.04.4 LTS(x86_64)
- Umami:3.4.0(镜像
ghcr.io/umami-software/umami:postgresql-latest,实测镜像内/app/package.json版本号3.4.0) - 数据库:PostgreSQL 16.15(镜像
postgres:16) - 编排:Docker Compose v2(compose.yml 单文件编排,一个自定义网络 + 两个服务)
- 对外端口:13006(宿主机)→ 3000(Umami 服务默认监听 0.0.0.0:3000)
- 数据目录:/opt/data/umami-doc-pg(PostgreSQL 数据落盘 bind mount)
- 管理账号:admin / q1w2e3r4(演示口令;首次启动自动创建 admin,默认口令 umami,本文演示改密为 q1w2e3r4)
docker --version && docker compose version
实测回显:
Docker version 29.8.0, build 30a94c6
Docker Compose version v2.39.4
二、镜像选型说明
Umami 官方在 ghcr.io 发布两族镜像,按所配数据库选择:
| 镜像 tag | 数据库 | 适用 |
|---|---|---|
ghcr.io/umami-software/umami:postgresql-latest | PostgreSQL | 官方推荐,本文实测选型(拉取即为 3.4.0) |
ghcr.io/umami-software/umami:mysql-latest | MySQL/MariaDB | 已有 MySQL 运维体系时 |
ghcr.io/umami-software/umami:postgresql-v3.4.0 | PostgreSQL | 需要锁版本时(tag 跟随 Release 版本号) |
ghcr.io/umami-software/umami:mysql-v3.4.0 | MySQL | 同上 |
关键差异与如何选:PostgreSQL 变体是官方文档与示例的默认路线(DATABASE_TYPE=postgresql),Prisma 迁移在两种库上都支持;本文选 postgresql-latest 搭配 postgres:16,理由:官方默认组合、迁移验证最充分、pg_dump 备份生态成熟。若要严格锁版本避免 latest 自动升级引入迁移变化,把 tag 换成 postgresql-v3.4.0 即可(升级时再改 tag)。
三、前置条件
- 一台 Ubuntu 22.04 服务器,已安装 Docker 24+ 与 Docker Compose v2(回显见上);
- 磁盘余量 ≥ 2GB(镜像约 500MB + 数据库初始数据);
- 13006 端口未被占用(可自行更换);
- 能访问 ghcr.io 拉取镜像(内网环境可提前
docker pull后导入)。
ss -lntp | grep 13006 || echo "port 13006 free"
实测回显:
port 13006 free
四、部署步骤
本节从空目录开始,到服务可用、改密、建站、统计跑通,全程命令逐条给出。
4.1 创建目录与编排文件
创建数据目录与工程目录(PostgreSQL 数据落盘到宿主机,保证重建服务不丢数据):
mkdir -p /opt/data/umami-doc-pg
mkdir -p /opt/umami-doc-lab
编写 /opt/umami-doc-lab/compose.yml(本文实测使用的原文件):
services:
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
container_name: umami-app
restart: unless-stopped
ports:
- "13006:3000"
environment:
DATABASE_URL: postgresql://umami:q1w2e3r4@umami-db:5432/umami
DATABASE_TYPE: postgresql
APP_SECRET: 9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d
DISABLE_TELEMETRY: 1
depends_on:
umami-db:
condition: service_healthy
umami-db:
image: postgres:16
container_name: umami-db
restart: unless-stopped
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: q1w2e3r4
volumes:
- /opt/data/umami-doc-pg:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami -d umami"]
interval: 5s
timeout: 5s
retries: 10
⚠️ 注意:
APP_SECRET必须替换为你自己的随机值(openssl rand -hex 32生成),它用于签发登录令牌;更换该值会使所有已登录令牌失效(本文第七章有实测)。演示口令 q1w2e3r4 仅用于文档示例,生产请使用强口令。
4.2 启动并确认就绪
cd /opt/umami-doc-lab
docker compose up -d
实测回显:
[+] Running 3/3
✔ Network umami-doc-lab_default Created
✔ Container umami-db Healthy
✔ Container umami-app Started
等待 20 秒左右让 Umami 完成首次迁移,确认两服务状态(umami-db 应为 healthy):
docker compose ps
实测回显:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
umami-app ghcr.io/umami-software/umami:postgresql-latest "docker-entrypoint.s…" umami 38 seconds ago Up 32 seconds 0.0.0.0:13006->3000/tcp, [::]:13006->3000/tcp
umami-db postgres:16 "docker-entrypoint.s…" umami-db 38 seconds ago Up 38 seconds (healthy) 5432/tcp
curl -s http://localhost:13006/api/heartbeat
实测回显:
{"ok":true}
4.3 首次启动日志(迁移过程)
首次启动会自动执行全部数据库迁移并初始化,日志实测(节选首尾):
docker compose logs umami | head -16
umami-app | ✓ DATABASE_URL is defined.
umami-app | ✓ Database connection successful.
umami-app | ✓ Database version check successful.
umami-app | Loaded Prisma config from prisma.config.ts.
umami-app |
umami-app | Prisma schema loaded from prisma/schema.prisma.
umami-app | Datasource "db": PostgreSQL database "umami", schema "public" at "umami-db:5432"
umami-app |
umami-app | 26 migrations found in prisma/migrations
umami-app |
umami-app | Applying migration `01_init`
umami-app | Applying migration `02_report_schema_session_data`
umami-app | Applying migration `03_metric_performance_index`
umami-app | Applying migration `04_team_redesign`
umami-app | Applying migration `05_add_visit_id`
umami-app | Applying migration `06_session_data`
docker compose logs umami | tail -8
umami-app | All migrations have been successfully applied.
umami-app |
umami-app | ✓ Database is up to date.
umami-app | ▲ Next.js 16.3.4
umami-app | - Local: http://localhost:3000
umami-app | - Network: http://0.0.0.0:3000
umami-app | ✓ Ready in 0ms
umami-app | ✓ Running next.config took 0.8ms
验证要点:All migrations have been successfully applied + ✓ Database is up to date 表示库结构就绪;服务监听 0.0.0.0:3000(经 ports 映射对外为 13006)。
4.4 首次登录与改密
浏览器访问 http://<服务器IP>:13006,用初始管理员 admin / umami 登录(全新空库首启自动创建,实测空库启动后 user 表即出现 username=admin、role=admin 一行)。命令行验证:
curl -s -X POST http://localhost:13006/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"umami"}' -o /dev/null -w 'HTTP=%{http_code}\n'
实测回显:
HTTP=200
登录后立即改密(控制台 Settings → Change password,或直接调 API):
TOKEN=$(curl -s -X POST http://localhost:13006/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"umami"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["token"])')
curl -s -X POST http://localhost:13006/api/me/password \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"currentPassword":"umami","newPassword":"q1w2e3r4"}'
实测回显(HTTP 200):
{"id":"41e2b680-648e-4b09-bcd7-3e2b10c06264","username":"admin","role":"admin","createdAt":"2026-10-17T09:29:10.286Z"}
验证改密生效(旧口令 401、新口令 200):
curl -s -X POST http://localhost:13006/api/auth/login -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"umami"}' -o /dev/null -w 'old=%{http_code}\n'
curl -s -X POST http://localhost:13006/api/auth/login -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"q1w2e3r4"}' -o /dev/null -w 'new=%{http_code}\n'
实测回显:
old=401
new=200
4.5 建站与嵌码验证
登录控制台 → Websites → Add website,或用 API 创建(替换 $TOKEN 为上一步新口令换取的令牌):
TOKEN=$(curl -s -X POST http://localhost:13006/api/auth/login -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"q1w2e3r4"}' | python3 -c 'import sys,json;print(json.load(sys.stdin)["token"])')
curl -s -X POST http://localhost:13006/api/websites \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Demo Site","domain":"demo.example.com"}'
实测回显(HTTP 200,记下返回的 id 即 website id,节选):
{"id":"bd69fdbf-f8bd-48c3-93d4-877c51c93fb4","name":"Demo Site","domain":"demo.example.com","resetAt":null,"userId":"41e2b680-648e-4b09-bcd7-3e2b10c06264","createdAt":"2026-10-17T11:00:12.086Z","shareId":null,...}
在目标网站页面嵌入统计脚本(data-website-id 填上一步的 id):
<script defer src="http://<服务器IP>:13006/script.js" data-website-id="bd69fdbf-f8bd-48c3-93d4-877c51c93fb4"></script>
确认脚本可被公网拉到(HTTP 200,4773 字节):
curl -s -o /dev/null -w 'HTTP=%{http_code} size=%{size_download}\n' http://localhost:13006/script.js
实测回显:
HTTP=200 size=4773
之后访问目标页面,控制台 Websites 即出现实时数据;上报与统计核对方法见《Umami 3.4 API 使用详解》。
五、数据持久化说明
- PostgreSQL 数据通过 bind mount 落盘在宿主机
/opt/data/umami-doc-pg(对应库内/var/lib/postgresql/data),docker compose down删除服务与网络但不删除该目录; - Umami 应用本身无状态(所有数据在数据库),应用服务可随时重建;
- 升级/重建后数据自动保留,见下方实测。
重建验证(down 后再 up,管理员账号仍在即数据未丢):
docker compose down && docker compose up -d
实测回显(节选):
Container umami-app Stopped
Container umami-app Removed
Container umami-db Stopped
Container umami-db Removed
Network umami-doc-lab_default Removed
Container umami-db Healthy
Container umami-app Starting
Container umami-app Started
sleep 20 && curl -s http://localhost:13006/api/heartbeat && echo \
&& docker exec umami-db psql -U umami -d umami -tc 'select username from "user";'
实测回显:
{"ok":true}
admin
验证要点:心跳 200、admin 账号仍在,down/up 重建不丢数据。
六、环境变量表
Umami 服务(umami 服务)常用环境变量(完整参数清单与逐项实测见《Umami 3.4 配置参数详解》):
| 变量 | 必填 | 示例值 | 说明 |
|---|---|---|---|
DATABASE_URL | ✅ | postgresql://umami:q1w2e3r4@umami-db:5432/umami | 数据库连接串,唯一必填项 |
DATABASE_TYPE | 镜像部署必填 | postgresql | 声明数据库类型(镜像构建/启动校验用) |
APP_SECRET | 强烈建议 | 64 位随机 hex | 登录令牌签名密钥,每实例唯一 |
PORT | 否 | 3000 | 服务监听端口(默认 3000) |
DISABLE_TELEMETRY | 否 | 1 | 关闭匿名遥测(本文实测开启) |
TRACKER_SCRIPT_NAME | 否 | analytics | 统计脚本改名,规避拦截器 |
COLLECT_API_ENDPOINT | 否 | /collect-test | 采集端点改名,规避拦截器 |
CLIENT_IP_HEADER | 否 | x-real-ip | 从指定头取客户端 IP(反代场景) |
PostgreSQL 服务(umami-db 服务)环境变量:
| 变量 | 说明 |
|---|---|
POSTGRES_DB | 初始化库名(umami) |
POSTGRES_USER | 初始化用户(umami) |
POSTGRES_PASSWORD | 初始密码(演示用 q1w2e3r4,生产用强口令) |
⚠️ 注意:
DATABASE_URL里的账号密码必须与 umami-db 的三项初始化值一致;密码含@、#等特殊字符时需做 URL 编码(如@写成%40),否则连接串解析失败。
七、配置参数说明
编排文件中与可用性直接相关的三项逐项说明:
- healthcheck(umami-db):
pg_isready -U umami -d umami每 5 秒探测一次,超时 5 秒、最多重试 10 次;这是depends_on.condition: service_healthy生效的前提。实测首启时序:umami-db 先进入healthy,umami-app 随后启动(见 4.2 回显的 STATUS 列)。 - depends_on + condition:保证库就绪后应用才启动,避免首启时迁移因连不上库而失败。
- restart: unless-stopped:两个服务都配置;宿主机重启或进程崩溃后自动拉起,手动
docker compose stop后不会自启。
Umami 应用自身无配置文件,全部行为由环境变量驱动;若需调整参数,改 environment: 段后执行 docker compose up -d 重建服务生效(重建期间采集中断数秒,统计脚本上报会自动重试)。
八、配置文件挂载
Umami 应用镜像内无可编辑的主配置文件(配置即环境变量),常规挂载只有两类:
- 数据库数据目录(本文已配置):
/opt/data/umami-doc-pg:/var/lib/postgresql/data; - 可选:GeoIP 库缓存目录:配置
GEO_DATABASE_URL时 Umami 会下载 MaxMind 兼容 MMDB;如需离线维护,可将宿主机目录挂到应用内对应路径并在GEO_DATABASE_URL指向本地文件。
💡 说明:Umami 的
compose.yml不需要挂载config文件——不要给 umami 服务挂任何"配置文件"目录;一切参数走environment:。
九、等价 docker run
不用 Compose 时,两条 docker run 完全等价于上述编排(先建网络,库先起):
docker network create umami-net
docker run -d --name umami-db --network umami-net --restart unless-stopped \
-e POSTGRES_DB=umami -e POSTGRES_USER=umami -e POSTGRES_PASSWORD=q1w2e3r4 \
-v /opt/data/umami-doc-pg:/var/lib/postgresql/data \
postgres:16
docker run -d --name umami-app --network umami-net --restart unless-stopped \
-p 13006:3000 \
-e DATABASE_URL=postgresql://umami:q1w2e3r4@umami-db:5432/umami \
-e DATABASE_TYPE=postgresql \
-e APP_SECRET=9f1c2d3e4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d \
-e DISABLE_TELEMETRY=1 \
ghcr.io/umami-software/umami:postgresql-latest
验证(等价部署同样通过心跳与健康检查确认):
sleep 25 && curl -s http://localhost:13006/api/heartbeat
实测回显:
{"ok":true}
💡 技术拓展:
--restart三种策略的取舍——
策略 行为 适用 no(默认)不自动重启 一次性任务 on-failure非零退出才重启,可限次数 可能预期失败的批处理 always总是重启(含 daemon 重启后) 无条件保活 unless-stopped同 always,但手动 stop 后不再拉起 常驻服务(本文选型) 本文选
unless-stopped:常驻保活的同时保留"手动停掉就不被拉起"的运维语义。
十、常用运维命令
以下命令均在 /opt/umami-doc-lab(compose.yml 所在目录)执行。
状态与日志:
docker compose ps # 服务状态(healthy/up)
docker compose logs -f umami # 跟踪应用日志(迁移、请求错误)
docker compose logs --tail 50 umami-db # 数据库日志
启停与重建:
docker compose restart umami # 仅重启应用(约数秒中断)
docker compose stop / start # 停止 / 启动(保留服务定义)
docker compose up -d # 改 environment 后重建生效
docker compose down # 停止并删除服务与网络(数据目录保留)
备份与恢复(PostgreSQL 逻辑备份,实测全库 gzip 后 7493 字节):
docker exec umami-db sh -c 'pg_dump -U umami -d umami | gzip > /tmp/umami-backup.sql.gz'
docker cp umami-db:/tmp/umami-backup.sql.gz /opt/backup/umami-backup.sql.gz
实测回显(容器内文件列表):
-rw-r--r-- 1 root root 7493 Oct 17 11:09 /tmp/umami-backup.sql.gz
恢复(空库还原):
docker cp /opt/backup/umami-backup.sql.gz umami-db:/tmp/
docker exec umami-db sh -c 'gunzip -c /tmp/umami-backup.sql.gz | psql -U umami -d umami'
升级(拉新镜像并重建,迁移自动执行;升级前先备份):
docker compose pull
docker compose up -d
docker compose logs --tail 20 umami # 确认 All migrations have been successfully applied
版本确认:
docker exec umami-app sh -c 'grep -m1 version /app/package.json'
实测回显:
"version": "3.4.0",
卸载(彻底清理):
docker compose down -v # 移除服务、网络
rm -rf /opt/data/umami-doc-pg /opt/umami-doc-lab # 删除数据落盘与工程目录
十一、常见问题
Q1:启动卡在迁移或报数据库连接失败?
看 docker compose logs umami:若出现 Can't reach database server,多数是 umami-db 尚未 healthy 就被应用连(未配置 depends_on.condition: service_healthy),或 DATABASE_URL 密码与 POSTGRES_PASSWORD 不一致/含未编码特殊字符。修正 environment 后 docker compose up -d 重建。
Q2:端口 13006 想换成 80/443?
改 ports 映射(如 "80:3000")后 docker compose up -d。443+域名场景建议前面再放一层 Nginx/Caddy 做 TLS 反代,Umami 本身只提供 HTTP。
Q3:忘记 admin 密码怎么办? 方法一:用另一管理员账号在控制台改;方法二:直接更新库表(先算好 bcrypt 哈希),示例:
python3 -c 'import bcrypt;print(bcrypt.hashpw(b"q1w2e3r4", bcrypt.gensalt(rounds=10)).decode())'
实测回显(60 字符 bcrypt 串,每次生成不同):
$2b$10$BUli0c.muyCW1ErNJc3jL.vFRtFJWrT8/GcR4A.sUdCznaXiqFXa
把生成的哈希写库(若 admin 行还在用 update;已彻底删除则 insert,实测两法均可):
docker exec umami-db psql -U umami -d umami -c \
"insert into \"user\" (user_id, username, password, role) values (gen_random_uuid(), 'admin', '$2b$10$BUli0c.muyCW1ErNJc3jL.vFRtFJWrT8/GcR4A.sUdCznaXiqFXa', 'admin');"
实测回显:
INSERT 0 1
验证(用哈希对应的明文 q1w2e3r4 登录):
curl -s -X POST http://localhost:13006/api/auth/login -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"q1w2e3r4"}' -o /dev/null -w 'reset_login=%{http_code}\n'
实测回显:
reset_login=200
⚠️ 注意:删除 admin 行后重启服务不会重新种出初始账号(实测:
delete from "user"后重启,user表为空,admin/umami 登录 401)——初始化种子只发生在数据库全新建库时。因此忘记密码只能走"另一管理员改密"或"SQL 写入 bcrypt 哈希"两条路。
Q4:升级后打不开或报表报错?
先看迁移日志是否完整(All migrations have been successfully applied)。若镜像 tag 用了 latest 且跨大版本升级,先确认 Release Notes 的迁移说明;回滚方式:把 tag 改回旧版本 docker compose up -d(注意:数据库已迁移到新结构时,跨版本回滚需用升级前的备份恢复库)。
Q5:统计脚本 404 或数据不上报?
确认 http://<host>:13006/script.js 返回 200(改过 TRACKER_SCRIPT_NAME 的,脚本路径变为 /<新名>,默认 script.js 不再是唯一路径);确认页面 data-website-id 与控制台 website id 一致;广告拦截器会拦默认路径,用 TRACKER_SCRIPT_NAME/COLLECT_API_ENDPOINT 改名规避(实测两参数均生效)。