Homarr Docker Compose 部署指南
Homarr v2.10.1 Docker Compose 全流程实测部署:secrets 注入加密密钥、首启迁移与种子日志取证、onboarding 向导 API 调用链、down/up 持久化验证、环境变量表与外部 PostgreSQL/Redis 变体,含缺密钥 500、DB_DIALECT 漏配、PG 启动竞态、CLI _FILE 密钥 4 条排障实录。
Homarr Docker Compose 部署指南
目标:在 Ubuntu 24.04.5 服务器上用 Docker Compose 部署 Homarr v2.10.1(ghcr 官方镜像),完成加密密钥生成与 secrets 注入、首启迁移与种子验证、onboarding 向导全流程、数据持久化闭环与外部 PostgreSQL/Redis 变体部署。本文全部命令与回显为 2026-11-02/03 实测,含缺 SECRET_ENCRYPTION_KEY 页面 500、外部 PG 漏配 DB_DIALECT 迁移失败、PostgreSQL 就绪竞态 ECONNREFUSED、CLI 读不到
_FILE密钥 4 条排障实录。
一、环境信息
| 项目 | 值 |
|---|---|
| 主机 OS | Ubuntu 24.04.5 LTS(内核 x86_64) |
| Docker | 29.9.2 |
| Docker Compose | v5.6.1 |
| 组件版本 | Homarr v2.10.1(GitHub Release,2026-10-30 发布) |
| 镜像 | ghcr.io/homarr-labs/homarr:v2.10.1(digest sha256:2844201f11ce...,磁盘占用 692MB) |
| 运行期 | Next.js 16.4.2 + nginx 1.30.6 + 内置 Redis 8.8.0 + Node.js v24.22.1(首启日志实测) |
| Web 端口 | 宿主 8138→7575(服务内部监听 7575) |
| 数据目录 | 宿主 /opt/apps/homarr/appdata 挂载至服务内 /appdata |
| 加密密钥 | /opt/apps/homarr/secrets/secret_key.txt,经 compose secrets 挂载为服务内 /run/secrets/secret_key |
| 网络 | 自定义 bridge homarr-net,显式网段 10.89.65.0/24,实例固定 IP 10.89.65.10 |
| 项目名 | compose 显式 name: homarr-2-10-1-stack |
| 演示账号 | onboarding 创建 admin / q1w2e3r4(邮箱 admin@homarr.local) |
💡 说明:Homarr 是单服务全栈应用(Web + 内置 nginx/Redis + SQLite 同进程组拉起,无外部数据库依赖),版本核实过程与选型背景见《Homarr 介绍与选型指南》。
二、镜像选型说明
官方文档推荐 Docker Compose 安装,交付形态为 GitHub Container Registry 上的 ghcr.io/homarr-labs/homarr。标签清单实查(2026-11-03,经 token 分页调用 ghcr.io/v2/homarr-labs/homarr/tags/list,共 259 个标签):
| 标签 | 含义 | 适用场景 |
|---|---|---|
latest | 跟随最新稳定版(当前即 2.10.1) | 愿意接受自动升级的用户 |
v2.10.1 | 应用版本号锁定(GitHub tag 同名) | 生产推荐,升级可控 |
v2.10.0 | 上一版(2026-10 月内发布) | 需要回退时显式指定 |
与 Docker Hub 类镜像不同,ghcr 上只有 latest 与应用版本号两类标签,没有 latest-dev/构建号后缀(实查 specials 仅 ['latest']);本地实测 v2.10.1 与 latest 当前指向同一 digest(sha256:2844201f11ce28b98183728dea2261de2b4fd8ed5480a4eef0b7977cc1a08dc0,692MB)。
关键差异与锁版本理由:Homarr 发版极勤——2026 年 10 月内 17 天发了 5 个版本(2.6.0→2.10.1),latest 漂移风险远高于同类面板;且 v2 大版本启动时会自动迁移数据库、不可回退(官方 Docker 文档明示「v1 cannot use the upgraded database」),生产环境一律锁应用版本标签,升级走第十节显式流程。
三、前置条件
3.1 端口空闲核实
部署前先确认计划使用的 8138 未被占用。部署时实测 81xx 段已占端口:
$ ss -ltn | awk '{print $4}' | grep -oE '[0-9]+$' | sort -n | uniq | grep -E '^81[0-9][0-9]$'
8128
8129
8130
8131
8133
8135
8136
8139
8142
8143
8147
8138 不在已占列表内,可用(部署完成后复核,8138 已由本服务绑定,外部 PostgreSQL 变体占用 8140):
$ ss -ltn | awk '{print $4}' | grep -oE '[0-9]+$' | sort -n | uniq | grep -E '^81[0-9][0-9]$'
8128
8129
8130
8131
8133
8135
8136
8138
8139
8140
8141
8142
8143
8144
8147
3.2 网段空闲核实
自定义网络需避开已占用的 10.89.x.0/24。部署前实测:10.89.65.0/24 空闲(当时已占含 62/56 等网段),本部署显式占用 65;后续外部 PostgreSQL 变体占用 66。部署完成后复核:
$ docker network ls -q | xargs -r docker network inspect --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}' | grep -E '10\.89\.'
heimdall-284-stack_heimdall-net 10.89.62.0/24
homarr-2-10-1-pg-stack_homarr-pg-net 10.89.66.0/24
homarr-2-10-1-stack_homarr-net 10.89.65.0/24
homer-v26083-stack_homer-net 10.89.56.0/24
littlelink-v3110-jane-stack_littlelink-net 10.89.69.0/24
littlelink-v3110-stack_littlelink-net 10.89.68.0/24
3.3 Docker 引擎就绪
$ docker version --format 'Docker {{.Server.Version}}'
Docker 29.9.2
$ docker compose version
Docker Compose version v5.6.1
四、部署步骤
4.1 建立项目目录、生成加密密钥与 compose 文件
SECRET_ENCRYPTION_KEY 是 Homarr 唯一的硬性启动要求:用于加密落库的集成凭据,官方指定生成方式即 openssl(缺失后果见 FAQ 11.1):
$ mkdir -p /opt/apps/homarr/compose /opt/apps/homarr/appdata /opt/apps/homarr/secrets
$ cd /opt/apps/homarr/compose
$ openssl rand -hex 32 | tee ../secrets/secret_key.txt
f74df640447b3dadc9d5d69ec55737cbe42ff9a6e214a5b04ba10eace2384b7b
本例把密钥写入独立文件、经 compose secrets 机制挂载(避免明文进 environment: 与 inspect 回显),对应变量用 _FILE 后缀——官方文档明确「With docker you can add the _FILE suffix to any environment variable, which will read the value from a file」。docker-compose.yml 全文:
name: homarr-2-10-1-stack
services:
homarr:
image: ghcr.io/homarr-labs/homarr:v2.10.1
container_name: homarr-2-10-1-server
restart: unless-stopped
ports:
- '8138:7575'
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /opt/apps/homarr/appdata:/appdata
environment:
- SECRET_ENCRYPTION_KEY_FILE=/run/secrets/secret_key
- LOG_LEVEL=debug
- NO_EXTERNAL_CONNECTION=true
secrets:
- secret_key
networks:
homarr-net:
ipv4_address: 10.89.65.10
secrets:
secret_key:
file: ../secrets/secret_key.txt
networks:
homarr-net:
driver: bridge
ipam:
config:
- subnet: 10.89.65.0/24
gateway: 10.89.65.1
逐项说明:
name:显式项目名,避免与其他项目目录同名互删;ports: '8138:7575':服务内部固定监听 7575(nginx),宿主端口自选;/var/run/docker.sock:/var/run/docker.sock:可选,仅当需要面板内 Docker 发现与容器启停管理时挂载(官方文档:「Mount the Docker socket only when Homarr needs Docker discovery or management」);/opt/apps/homarr/appdata:/appdata:全部持久化数据(数据库、内置 Redis 持久化、受信证书)都落在/appdata,见第五节;SECRET_ENCRYPTION_KEY_FILE:从 secrets 挂载文件读取密钥,生效日志见第八节;LOG_LEVEL=debug:本批次为取证设为 debug(生产默认 info 即可,见第六节);NO_EXTERNAL_CONNECTION=true:关闭更新检查、天气、位置搜索、RSS 等一切外呼(官方环境变量表实测对照见第六节);ipv4_address: 10.89.65.10:固定实例 IP,便于反代与集成 URL 引用。
4.2 拉取镜像
$ docker compose pull
Image ghcr.io/homarr-labs/homarr:v2.10.1 Pulling
Image ghcr.io/homarr-labs/homarr:v2.10.1 Pulled
本地实况核对(digest 与第二节实查一致):
$ docker images --digests | grep -E 'homarr|REPOSITORY'
REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
ghcr.io/homarr-labs/homarr v2.10.1 sha256:2844201f11ce28b98183728dea2261de2b4fd8ed5480a4eef0b7977cc1a08dc0 2844201f11ce 2 days ago 692MB
4.3 启动栈
$ docker compose up -d
Network homarr-2-10-1-stack_homarr-net Creating
Network homarr-2-10-1-stack_homarr-net Created
Container homarr-2-10-1-server Creating
Container homarr-2-10-1-server Created
Container homarr-2-10-1-server Starting
Container homarr-2-10-1-server Started
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
homarr-2-10-1-server ghcr.io/homarr-labs/homarr:v2.10.1 "/app/entrypoint.sh …" homarr 31 seconds ago Up 30 seconds (healthy) 0.0.0.0:8138->7575/tcp, [::]:8138->7575/tcp
启动约 30 秒即进入 healthy——服务自带健康检查探测 /api/health/live。
4.4 首启迁移与种子过程(日志取证)
全新数据目录首启,启动脚本自动完成数据库迁移与种子数据,无需任何手工初始化:
$ docker logs homarr-2-10-1-server 2>&1 | head -46
Starting with UID='0', GID='0'
Running DB migrations
Created group 'everyone' through seed
Created onboarding step through seed
Created serverSetting through seed key=analytics
Created serverSetting through seed key=crawlingAndIndexing
Created serverSetting through seed key=board
Created serverSetting through seed key=user
Created serverSetting through seed key=appearance
Skipping seeding for serverSetting as it already exists key=branding
Created serverSetting through seed key=culture
Created serverSetting through seed key=search
Created 3 default search engines through seeding process
Created 4 default apps through seeding process
Set default board as home board for everyone group
Created default board 'dashboard' through seed
Created 5 bundled custom widgets through seeding process
Placed 7 widgets on board
Migrated release widget providers to options count="0"
Migrating app items with showDescriptionTooltip to descriptionDisplayMode count=0
Migrated app items with showDescriptionTooltip to descriptionDisplayMode count=0
Migrating releases widget options count=0
Migrated releases widget options count=0
Migration complete
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Starting internal Redis server
35:C 02 Nov 2026 17:23:15.395 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
35:C 02 Nov 2026 17:23:15.395 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
35:C 02 Nov 2026 17:23:15.395 * Redis version=8.8.0, bits=64, commit=00000000, modified=1, pid=35, just started
35:C 02 Nov 2026 17:23:15.395 * Configuration loaded
35:M 02 Nov 2026 17:23:15.396 * Increased maximum number of open files to 10032 (it was originally set to 1024).
35:M 02 Nov 2026 17:23:15.396 * monotonic clock: POSIX clock_gettime
35:M 02 Nov 2026 17:23:15.396 * Running mode=standalone, port=6379.
35:M 02 Nov 2026 17:23:15.396 * Server initialized
35:M 02 Nov 2026 17:23:15.396 * Ready to accept connections tcp
35:M 02 Nov 2026 17:23:15.396 # WARNING: Redis does not require authentication. Redis will accept connections from any local client.
▲ Next.js 16.4.2
- Local: http://localhost:3000
- Network: http://0.0.0.0:3000
✓ Ready in 0ms
✓ Running next.config took 1.1ms
2026-11-02T17:23:16.013Z info: Starting schedule of cron job. module="cronJobs" name="analytics"
2026-11-02T17:23:16.017Z info: ✅ WebSocket Server listening on ws://localhost:3001 module="websocketMain"
关键行解读:Running DB migrations → Migration complete 为 drizzle 迁移全流程;8 个 serverSetting 种子键对应服务器设置初始结构(详解见《Homarr 2.10.1 配置参数详解》);Created default board 'dashboard' 与 Placed 7 widgets on board 说明出厂自带一个示例看板;Starting internal Redis server 证实 Redis 8.8.0 内置拉起(无需外部部署);Next.js 16.4.2 监听 3000、WebSocket 3001,对外统一走 nginx 的 7575。
💡 说明:日志中
Memory overcommit must be enabled!为 Redis 常规提示,宿主sysctl vm.overcommit_memory=1可消除;Redis does not require authentication仅指内部回环端口 6379,未映射到宿主,无暴露面。
4.5 健康与入口验证
$ curl -s -o /dev/null -w 'live: %{http_code}\n' http://127.0.0.1:8138/api/health/live
live: 200
$ curl -s -o /dev/null -w 'root: %{http_code} %{redirect_url}\n' -m 8 http://127.0.0.1:8138/
root: 307 http://127.0.0.1:8138/auth/login
/api/health/live 是官方文档明示的免认证健康端点(依赖全部健康返回 200,否则 500)。GET / 返回 307:本实例已完成 onboarding(4.6 节),未登录访客被重定向到登录页 /auth/login;全新未初始化实例则重定向到向导页 /init(第九节独立实例实测可见)。
4.6 onboarding 向导(首启初始化)
浏览器访问 http://<服务器>:8138 即进入五步向导(start → user →(group)→ setup → finish):创建首位管理员(本例 admin / q1w2e3r4,邮箱 admin@homarr.local)、可选接入外部认证组、设置服务器默认语言/配色与首个看板名称。向导底层是「onboarding claim 会话 + tRPC」两段式协议,实测 API 调用链如下(为不打扰已上线实例,在回环 7575 的全新实例上取证):
$ B=http://127.0.0.1:7575
$ rm -f cj.txt
# 1. claim 领取 onboarding 会话(响应 Set-Cookie: homarr-onboarding-claim=...,后续请求必须带 cookie jar)
$ curl -s -i -c cj.txt -X POST $B/api/onboarding/claim | grep -iE 'set-cookie|^\{'
set-cookie: homarr-onboarding-claim=Xt4KqoNCNdrrXNvYc1xkPavRB0uY9QnXvrp15zVYO5Q; Path=/; Expires=Mon, 02 Nov 2026 21:26:14 GMT; HttpOnly; SameSite=strict
{"status":"issued","expiresAt":1793654774163}
# 不带 cookie 直接调 tRPC 一律被拒(实测对照):
$ curl -s -X POST $B/api/trpc/onboard.nextStep -H 'Content-Type: application/json' --data '{"json":null}'
{"error":{"json":{"message":"This onboarding session is not claimed.","code":-32003,"data":{"code":"FORBIDDEN","httpStatus":403,"path":"onboard.nextStep","zodError":null,"error":null,"isBoardLayoutBoundsError":false}}}}
# 2. 步骤推进 start -> user
$ curl -s -b cj.txt -X POST $B/api/trpc/onboard.nextStep -H 'Content-Type: application/json' --data '{"json":null}' | head -c 200; echo
{"result":{"data":{"json":null,"meta":{"values":["undefined"],"v":1}}}}
$ curl -s -b cj.txt "$B/api/trpc/onboard.currentStep"
{"result":{"data":{"json":{"current":"user","previous":"start"}}}}
# 3. 创建管理员:字段为 username/email/password/confirmPassword(漏传 confirmPassword 的 400 实录见下)
$ curl -s -b cj.txt -X POST $B/api/trpc/user.initUser -H 'Content-Type: application/json' --data '{"json":{"username":"admin","email":"admin@homarr.local","password":"q1w2e3r4","confirmPassword":"q1w2e3r4"}}' | head -c 200; echo
{"result":{"data":{"json":null,"meta":{"values":["undefined"],"v":1}}}}
$ curl -s -b cj.txt -X POST $B/api/trpc/user.initUser -H 'Content-Type: application/json' --data '{"json":{"name":"admin","email":"admin@homarr.local","password":"q1w2e3r4"}}' | head -c 300; echo
{"error":{"json":{"message":"[\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"username\"\n ],\n \"message\": \"Invalid input: expected string, received undefined\"\n },\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"confirmPassword\"\n ...
# 4. credentials 登录验证(表单字段是 name,不是 username)
$ CSRF=$(curl -s -b cj.txt -c cj.txt $B/api/auth/csrf | python3 -c 'import sys,json;print(json.load(sys.stdin)["csrfToken"])')
$ curl -s -o /dev/null -w 'login: %{http_code} -> %{redirect_url}\n' -b cj.txt -c cj.txt -X POST $B/api/auth/callback/credentials --data-urlencode "csrfToken=$CSRF" --data-urlencode "name=admin" --data-urlencode "password=q1w2e3r4" --data-urlencode "callbackUrl=$B/"
login: 302 -> http://localhost:7575/
# 5. 完成 setup(返回 boardId),推进到 finish
$ curl -s -b cj.txt -X POST $B/api/trpc/onboard.completeSetup -H 'Content-Type: application/json' --data '{"json":{"server":{"defaultLocale":"en","defaultColorScheme":"dark"},"board":{"name":"dashboard","primaryColor":"#ff0055","secondaryColor":"#00ffb2","itemRadius":"md","layoutPreset":"balanced"}}}' | head -c 300; echo
{"result":{"data":{"json":{"boardId":"zjtb3tbie1n0004d78pysi28","boardName":"dashboard","docker":{"missingSourceIds":[],"ignoredSourceIds":[],"skippedWidgets":[]}}}}}
$ curl -s -b cj.txt -X POST $B/api/trpc/onboard.nextStep -H 'Content-Type: application/json' --data '{"json":null}' | head -c 200; echo
{"result":{"data":{"json":{"current":"finish","previous":"setup"}}}}
$ curl -s -o /dev/null -w 'GET /: %{http_code}\n' -b cj.txt $B/
GET /: 200
要点:claim 领取的 homarr-onboarding-claim cookie 有效期 4 小时(expiresAt 实测约 +4h),所有向导 tRPC 调用必须携带,否则一律 FORBIDDEN: This onboarding session is not claimed.(403);user.initUser 必填 username+confirmPassword(400 实录见上);登录回调字段名是 name;向导完成后 onboard.getState 过程即下线(实测返回 NOT_FOUND: No procedure found on path "onboard.getState"),重复访问 / 不再进 /init。
4.7 down/up 持久化验证
$ docker compose down
Container homarr-2-10-1-server Stopping
Container homarr-2-10-1-server Stopped
Container homarr-2-10-1-server Removing
Container homarr-2-10-1-server Removed
Network homarr-2-10-1-stack_homarr-net Removing
Network homarr-2-10-1-stack_homarr-net Removed
$ docker compose up -d
Container homarr-2-10-1-server Starting
Container homarr-2-10-1-server Started
重启日志显示迁移与种子全部走「已存在跳过」分支、Redis 从磁盘快照恢复,数据无损:
$ docker logs homarr-2-10-1-server 2>&1 | sed -n '1,20p'
Starting with UID='0', GID='0'
Running DB migrations
Skipping seeding of group 'everyone' as it already exists
Skipping seeding of onboarding as it already exists
Skipping seeding for serverSetting as it already exists key=analytics
Skipping seeding for serverSetting as it already exists key=crawlingAndIndexing
Skipping seeding for serverSetting as it already exists key=board
Skipping seeding for serverSetting as it already exists key=user
Skipping seeding for serverSetting as it already exists key=appearance
Skipping seeding for serverSetting as it already exists key=branding
Skipping seeding for serverSetting as it already exists key=culture
Skipping seeding for serverSetting as it already exists key=search
Skipping seeding of default search engines as some already exists
Skipping seeding of default apps as some already exist
Skipping seeding of default board as one already exists
Skipping seeding of bundled custom widgets because they already exist
Skipping seeding of board widgets as some already exist
Migration complete
Starting internal Redis server
35:C 02 Nov 2026 16:46:19.956 * Loading RDB produced by version 8.8.0
Loading RDB / Done loading RDB, keys loaded: 1 证实内置 Redis 的持久化文件(/appdata/redis/dump.rdb)随卷恢复;入口验证 GET / 依旧 307 → /auth/login(见 4.5),管理员 admin/q1w2e3r4 可正常登录。
五、数据持久化说明
全部状态收敛在 /appdata 卷(宿主 /opt/apps/homarr/appdata)。运行约一小时后实况:
$ ls -la appdata/
total 20
drwxr-xr-x 5 root root 4096 Nov 3 00:45 .
drwxr-xr-x 8 root root 4096 Nov 3 01:42 ..
drwxr-xr-x 2 root root 4096 Nov 3 01:36 db
drwxr-xr-x 2 root root 4096 Nov 3 01:47 redis
drwxr-xr-x 2 root root 4096 Nov 3 00:45 trusted-certificates
$ ls -la appdata/db/ appdata/redis/
appdata/db/:
-rw-r--r-- 1 root root 7077888 Nov 3 01:36 db.sqlite
appdata/redis/:
-rw-r--r-- 1 root root 167607 Nov 3 01:47 dump.rdb
$ du -sh appdata/*
6.8M appdata/db
168K appdata/redis
4.0K appdata/trusted-certificates
| 路径 | 内容 | 备份优先级 |
|---|---|---|
db/db.sqlite | 唯一数据库:看板、应用、集成、用户/分组/权限、服务器设置、API key、会话 | 必背 |
redis/dump.rdb | 内置 Redis 持久化快照(缓存/任务态) | 建议 |
trusted-certificates/ | 受信根证书(证书自动化写入,见官方 API 文档) | 建议 |
宿主 secrets/secret_key.txt | SECRET_ENCRYPTION_KEY——集成凭据加密根密钥 | 必背(丢失则已存凭据不可解) |
💡 说明:备份即打包
/appdata目录 + 密钥文件:tar czf homarr-appdata-$(date +%F).tar.gz -C /opt/apps/homarr appdata secrets。官方文档要求「Stop Homarr before copying the complete /appdata directory or volume」——停栈后再拷贝;若使用外部 PostgreSQL,须用其原生工具(如 pg_dump)另行备份。Web 界面 Management → Tools → Backup 还提供一键导出 zip(实测导出 2 457 332 字节完整包,见 CLI/API 篇)。
六、环境变量表
服务生效环境变量(inspect 实测回显 + 官方环境变量文档对照):
$ docker inspect homarr-2-10-1-server --format '{{json .Config.Env}}'
["SECRET_ENCRYPTION_KEY_FILE=/run/secrets/secret_key","LOG_LEVEL=debug","NO_EXTERNAL_CONNECTION=true","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","DB_URL=/appdata/db/db.sqlite","DB_DIALECT=sqlite","DB_DRIVER=better-sqlite3","AUTH_PROVIDERS=credentials","REDIS_IS_EXTERNAL=false","NODE_ENV=production"]
回显中后 6 项为服务内置默认值(compose 未设置),与官方文档默认一致。核心变量逐项说明:
| 变量 | 实测值 | 官方默认 | 说明 |
|---|---|---|---|
SECRET_ENCRYPTION_KEY | 经 _FILE 注入 | 无(必填) | 集成凭据加密根密钥,64 位 hex;缺失后果见 FAQ 11.1;任何变量均可加 _FILE 后缀从文件读取(docker secrets 场景) |
DB_URL | /appdata/db/db.sqlite | 同左 | 数据库 URL,默认 SQLite 落 /appdata |
DB_DIALECT | sqlite | sqlite | 迁移包方言选择:sqlite / postgresql(漏配坑见 FAQ 11.2) |
DB_DRIVER | better-sqlite3 | better-sqlite3 | 驱动:better-sqlite3 / node-postgres;MySQL 在 v2 被启动脚本直接拒绝 |
AUTH_PROVIDERS | credentials | credentials | 认证提供器,可扩展 OIDC/LDAP 单点登录(配置详解见《Homarr 2.10.1 配置参数详解》) |
REDIS_IS_EXTERNAL | false | false | 为 true 时不拉起内置 Redis,改连 REDIS_HOST/REDIS_PORT 外部实例 |
LOG_LEVEL | debug | info | debug / info / warn / error;debug 下日志含逐条 SQL(4.4 取证即 debug 效果) |
NO_EXTERNAL_CONNECTION | true | false | 关闭更新检查、天气、位置搜索、RSS 等外呼 |
PUID / PGID | 未设置(默认 0) | 0 / 0 | 改运行身份需同时保证 /appdata 与所挂 socket 权限正确(实测见下) |
ENABLE_DOCKER | 未设置(默认 true) | true | 设 false 隐藏 Docker 管理页并禁用容器 API 路由 |
BASE_URL | 未设置 | - | TLS 终结反代后的公网 HTTPS origin(secure cookie/origin 校验用) |
PUID/PGID 实测(换 1000:1000 重启):
$ docker logs homarr-2-10-1-puid 2>&1 | head -3
Starting with UID='1000', GID='1000'
Changing owner to 1000:1000...
Changing owner to 1000:1000, done.
健康检查依旧 200(puid health: 200)。
七、配置参数说明
Homarr 的配置面是三层模型(全部参数逐项详解见《Homarr 2.10.1 配置参数详解》):
- 环境变量(启动层):第六节所列,只影响进程行为(密钥、数据库/Redis 驱动、日志、外呼开关、Docker 端点发现等),修改需重建实例生效;
- 数据库(业务层):看板布局、应用卡片、集成、用户/分组/权限、
serverSetting8 组服务器设置、每板 16 键看板设置、用户偏好——全部存数据库、走 GUI/tRPC/API 管理,没有业务配置文件; - GUI 管理面:Management 菜单(Settings/Tools/Users/Integrations…)是业务层的唯一日常入口。
这一层设计与 Homer/Homepage 的「改 YAML 即生效」相反:好处是在线编辑、多用户分权、API 可自动化;代价是业务配置不可文件化 diff,备份必须落到数据库层面(第五节)。
八、配置文件挂载
本部署唯一的「配置文件」是加密密钥,经 compose secrets 以只读 bind 方式挂入:
$ docker inspect homarr-2-10-1-server --format '{{json .Mounts}}'
[{"Type":"bind","Source":"/opt/apps/homarr/appdata","Destination":"/appdata","Mode":"rw","RW":true,"Propagation":"rprivate"},{"Type":"bind","Source":"/opt/apps/homarr/secrets/secret_key.txt","Destination":"/run/secrets/secret_key","Mode":"","RW":false,"Propagation":"rprivate"},{"Type":"bind","Source":"/var/run/docker.sock","Destination":"/var/run/docker.sock","Mode":"rw","RW":true,"Propagation":"rprivate"}]
密钥挂载点 /run/secrets/secret_key 为只读("RW":false)。启动脚本按 _FILE 后缀解析并加载,日志实证:
Info: Loaded secret for SECRET_ENCRYPTION_KEY from /run/secrets/secret_key
除密钥外没有任何需要手工挂载的配置文件:nginx 配置由启动脚本按模板(IPv6 探测、可信代理)现场生成,redis.conf 内置,业务配置全在数据库(第七节)。
九、等价 docker run
compose 与 docker run 参数等价。独立实例实测(回环 7575、独立数据目录,验证与 compose 栈并行无冲突):
$ rm -rf appdata-run && mkdir -p appdata-run
$ docker run -d --name homarr-2-10-1-run --restart unless-stopped -p 127.0.0.1:7575:7575 \
-v /opt/apps/homarr/appdata-run:/appdata \
-e SECRET_ENCRYPTION_KEY=f74df640447b3dadc9d5d69ec55737cbe42ff9a6e214a5b04ba10eace2384b7b \
ghcr.io/homarr-labs/homarr:v2.10.1
1ad76f3cb25b867790f38e18b04692860b889a5b328cf07723b57047883b8b0e
$ for i in $(seq 1 60); do [ "$(curl -s -o /dev/null -w '%{http_code}' -m 3 http://127.0.0.1:7575/api/health/live)" = "200" ] && { echo "health=200 after ${i}s"; break; }; sleep 1; done
health=200 after 2s
$ curl -s -o /dev/null -w 'GET /: %{http_code} -> %{redirect_url}\n' -m 8 http://127.0.0.1:7575/
GET /: 307 -> http://127.0.0.1:7575/init
两处与 compose 栈的差异即参数等价性的体现:密钥直接 -e 传入(与 secrets _FILE 二选一);GET / 重定向到 /init 向导页——全新数据目录尚未 onboarding(对照 4.5 已完成向导的实例重定向 /auth/login)。验证完清理:
$ docker rm -f homarr-2-10-1-run
homarr-2-10-1-run
十、常用运维命令
# 状态、资源
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
homarr-2-10-1-server ghcr.io/homarr-labs/homarr:v2.10.1 "/app/entrypoint.sh …" homarr 31 seconds ago Up 30 seconds (healthy) 0.0.0.0:8138->7575/tcp, [::]:8138->7575/tcp
$ docker stats homarr-2-10-1-server --no-stream --format '{{.Name}} CPU={{.CPUPerc}} MEM={{.MemUsage}}'
homarr-2-10-1-server CPU=0.06% MEM=157MiB / 15.57GiB
# 日志(debug 级别含逐条 SQL,见第六节)
$ docker compose logs --tail 5
homarr-2-10-1-server | 2026-11-02T17:25:00.004Z debug: Executed SQL query module="db" query="select "setting_key", "value" from "serverSetting" "serverSettings" where "serverSettings"."setting_key" = ? limit ?"
homarr-2-10-1-server | 2026-11-02T17:25:00.005Z debug: The callback of cron job succeeded module="cronJobs" name="ping" beforeCallbackTook="0 ms" callbackTook="2 ms"
# 升级:改 compose 里的版本标签(如 v2.10.1 -> 新版本号)后
$ docker compose pull
$ docker compose up -d
# 版本确认(免认证信息端点,ApiKey 亦可):
$ curl -s -H "ApiKey: a2a9dbf6.6b2e9362728ba1c909ab165fd3aba3e015f7d80c03adc363" http://127.0.0.1:8138/api/info
{"version":"2.10.1"}
# 停止与卸载(数据在宿主 /opt/apps/homarr/appdata,down 不删数据)
$ docker compose down
升级纪律(官方 Docker 文档):升级前备份 /appdata 与密钥;新版本启动时自动跑数据库迁移,无需手工升级步骤;v1→v2 迁移不可逆——只回退版本号不换回升级前备份不构成回滚。运维若需在服务环境里跑内置 recovery CLI(账号恢复类操作),注意其密钥读取坑与挂起怪癖,详见《Homarr 2.10.1 常用 CLI 命令与工具详解》。
十一、常见问题
11.1 没配 SECRET_ENCRYPTION_KEY:页面 500,进程不退出
现象:删掉密钥变量重启后,GET / 返回 500;进程与 Next.js 都活着(官方文档描述为「容器会退出」,实测行为是存活但服务不可用)。启动日志实录:
▲ Next.js 16.4.2
- Local: http://localhost:3000
- Network: http://0.0.0.0:3000
✓ Ready in 0ms
✓ Running next.config took 1.7ms
❌ Invalid environment variables: [
{
expected: 'string',
code: 'invalid_type',
path: [ 'SECRET_ENCRYPTION_KEY' ],
message: 'SECRET_ENCRYPTION_KEY is required, please generate a 64 character secret in hex format or use the following: "28f30d39ef91a0b1d228f1f97dc7ac70716479f64da0f509c8be2ebc9898caa0"'
}
]
2026-11-02T16:45:46.722Z error: Failed to load embedded tasks service module="instrumentation"
at _ (/app/apps/nextjs/.next/server/chunks/_0qpzzynu152jt._.js:1:290)
at async Promise.all (index 0)
...(堆栈节选)
定位:环境变量 zod 校验失败发生在应用层(instrumentation 加载期),故 Web 进程照常监听、页面 500。解决:按 4.1 生成并注入密钥(_FILE 或直接 -e)。注意报错信息里给出的随机建议密钥每次启动都不同,照抄它没有意义——密钥必须持久化固定,否则更换密钥后已存集成凭据全部不可解。
11.2 外部 PostgreSQL 只配了 DB_DRIVER,漏配 DB_DIALECT:迁移报 SqliteError
现象:compose 里给了 DB_DRIVER=node-postgres 与 DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASSWORD,唯独漏了 DB_DIALECT=postgresql,启动即失败退出:
Running DB migrations
(种子日志节选,首尾实测见 4.4)
Migration failed SqliteError: no such function: now
at Database2.prepare (/app/db/migrations/sqlite/migrate.cjs:26856:26)
at BetterSQLiteSession.prepareQuery (/app/db/migrations/sqlite/migrate.cjs:97941:30)
...(堆栈节选)
code: 'SQLITE_ERROR'
}
ERROR: DB migrations failed, aborting startup
定位:启动脚本按 node ./db/migrations/$DB_DIALECT/migrate.cjs 选择迁移包,DB_DIALECT 默认 sqlite(第六节实测)——漏配时拿着 PG 连接串跑的是 SQLite 迁移包,方言错乱报 SqliteError。解决:DB_DIALECT 与 DB_DRIVER 必须成对设置(postgresql + node-postgres),完整写法见第十二节外部 PG 变体 compose 与《Homarr 2.10.1 配置参数详解》。
11.3 外部 PostgreSQL 就绪竞态:ECONNREFUSED
现象:PG 栈首次 up 时,Homarr 迁移比数据库就绪快,直接失败:
Migration failed _DrizzleQueryError: Failed query: CREATE SCHEMA IF NOT EXISTS "drizzle"
params:
at NodePgPreparedQuery.queryWithCache (/app/db/migrations/postgresql/migrate.cjs:93161:15)
...(堆栈节选)
cause: Error: connect ECONNREFUSED 10.89.66.2:5432
...
errno: -111,
code: 'ECONNREFUSED',
syscall: 'connect',
address: '10.89.66.2',
port: 5432
}
ERROR: DB migrations failed, aborting startup
定位:10.89.66.2:5432 即同网段 PostgreSQL 实例;首次初始化期间数据库还在做自身 initdb,Homarr 迁移已发起 TCP 连接。解决:compose 中用健康检查门控启动顺序(本批次外部 PG 变体的写法,实测重启后迁移一次通过):
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres 侧配 pg_isready healthcheck、redis 侧配 redis-cli ping healthcheck(全文见《Homarr 2.10.1 配置参数详解》外部变体章节)。
11.4 跑内置 recovery CLI 读不到 _FILE 密钥
现象:本部署密钥经 SECRET_ENCRYPTION_KEY_FILE 注入,直接进服务环境执行 homarr users list 报错:
❌ Invalid environment variables: [
{
expected: 'string',
code: 'invalid_type',
path: [ 'SECRET_ENCRYPTION_KEY' ],
message: 'SECRET_ENCRYPTION_KEY is required, please generate a 64 character secret in hex format or use the following: "bd71f854d032e0c1945955c86f999eb865db7efafad625e9c741d17f0cf29931"'
}
]
定位:Web 进程会解析 _FILE 后缀,recovery CLI 只认 SECRET_ENCRYPTION_KEY 本体。解决:执行时显式传入密钥值:
$ docker exec -e SECRET_ENCRYPTION_KEY="$(cat /opt/apps/homarr/secrets/secret_key.txt)" homarr-2-10-1-server homarr users list
CLI 命令族、输出后进程不退出的挂起怪癖与完整坑位见《Homarr 2.10.1 常用 CLI 命令与工具详解》。
十二、参考链接
- 官网与文档:https://homarr.dev
- Docker 安装文档:https://homarr.dev/docs/getting-started/installation/docker
- 环境变量文档:https://homarr.dev/docs/advanced/configuration/environment-variables
- GitHub 仓库:https://github.com/homarr-labs/homarr
- 镜像仓库:https://ghcr.io/homarr-labs/homarr
- 站内相关:《Homarr 介绍与选型指南》、《Homarr 2.10.1 配置参数详解》、《Homarr 2.10.1 常用 CLI 命令与工具详解》