Umami Docker Compose 部署指南

在 Ubuntu 22.04 上用 Docker Compose 从零部署 Umami 3.4.0 + PostgreSQL 16:镜像选型、编排文件逐项讲解、初始化验证、升级备份与常见问题排障,全程实测回显。

UmamiUmami2026/10/17

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-latestPostgreSQL官方推荐,本文实测选型(拉取即为 3.4.0)
ghcr.io/umami-software/umami:mysql-latestMySQL/MariaDB已有 MySQL 运维体系时
ghcr.io/umami-software/umami:postgresql-v3.4.0PostgreSQL需要锁版本时(tag 跟随 Release 版本号)
ghcr.io/umami-software/umami:mysql-v3.4.0MySQL同上

关键差异与如何选: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 应用镜像内无可编辑的主配置文件(配置即环境变量),常规挂载只有两类:

  1. 数据库数据目录(本文已配置):/opt/data/umami-doc-pg:/var/lib/postgresql/data;
  2. 可选: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 改名规避(实测两参数均生效)。

十二、参考链接