本文主要说明自托管媒体库服务(以 Octans 为例)如何用一个应用镜像完成私有化部署:SQLite / PostgreSQL、可选 nginx 与 GPU 都通过运行参数或 Compose overlay 选择;并覆盖卷挂载、端口、环境变量占位、就绪探针、硬解设备映射,以及镜像 tag 通道的最小公开口径。

核心交付只有一个应用镜像,同时包含后端、Web 静态资源与专用 FFmpeg runtime。数据库、反向代理和 GPU 不是镜像 tag 维度,而是部署时叠加的运行配置。镜像地址统一写成占位:

<registry>/<image>:<tag>

例如稳定通道:

<registry>/<image>:stable

固定版本部署用 SemVer 固定 tag(Docker tag 去掉 Git 的 v 前缀),不要把 sqlite / postgres / nginx / gpu 写进镜像 tag。

适用范围

覆盖:

  • Docker CLI 最小启动(SQLite 默认,或外接 PostgreSQL)。
  • 仓库提供的 Compose 参考:SQLite、PostgreSQL(外接库 / 参考库容器)、nginx overlay、GPU overlay。
  • 数据卷、媒体只读挂载、权限(PUID / PGID)、时区、健康检查与基础排障。
  • 硬件加速时如何把 /dev/dri/renderD* 映射进容器。

不覆盖:

  • Kubernetes / Helm。
  • PostgreSQL 运维、备份与 HA(见系列中的 PostgreSQL 专文)。
  • TLS 证书签发与公网网关完整运维。
  • SQLite ↔ PostgreSQL 数据搬迁。
  • 完整 CI secrets、runner 权限与手工推送 runbook(附录只保留通道与不可变 tag 原则)。

镜像与通道

通道tag 形态是否移动用途
稳定固定版<version>,如 0.2.2生产可钉死版本
稳定追溯stable-<version>-<shortsha>digest 对照与复现
稳定移动stable默认「跟最新稳定」
RC<version>-rc.<n>rcrc-…fixed 否 / rc候选验证
主线快照edgeedge-YYYYMMDDHHMMSS-<shortsha>edge跟踪 main,不建议生产默认

原则:

  • Git release tag 形如 vMAJOR.MINOR.PATCH,Docker fixed tag 去掉 v
  • 稳定版默认跟 stable;需要可回滚时钉 fixed tag 或 trace tag。
  • 历史兼容里可能存在的 latest / sha-* 不再作为新发布主通道。
  • 镜像内同时带 ASP.NET Core runtime 与专用 FFmpeg;运行配置(库、代理、GPU)不拆镜像。

Docker CLI 最小部署

SQLite(默认)

不配置 Database__Provider 时默认 SQLite,库文件在数据卷内:

<data-root>/db/sqlite3/octans.db
docker run -d \
  --name octans \
  --restart unless-stopped \
  -p 7777:7777 \
  -e TZ="Asia/Shanghai" \
  -v <data-root>:<data-root> \
  -v /path/to/media:/media:ro \
  <registry>/<image>:<tag>

就绪检查:

curl http://127.0.0.1:7777/api/ready

期望 providerSqlite(或等价就绪结构),表示已连库并完成 schema readiness。

PostgreSQL(外接库)

只要求能连上 PostgreSQL 16+(自建、云库、宿主机均可):

docker run -d \
  --name octans \
  --restart unless-stopped \
  -p 7777:7777 \
  -e TZ="Asia/Shanghai" \
  -e Database__Provider=Postgres \
  -e Database__Postgres__Url="postgresql://octans:<pg-password>@<postgres-host>:5432/octans" \
  -e Database__Postgres__SslMode="Prefer" \
  -v <data-root>:<data-root> \
  -v /path/to/media:/media:ro \
  <registry>/<image>:<tag>

Database__Postgres__SslMode 必须显式配置。常见取值:

取值说明
Disable无 TLS,可信内网 / 本机测试
Prefer优先 TLS,可协商回退
Require强制 TLS,不校验证书链
VerifyCA / VerifyFull强制 TLS 并校验 CA / 主机名

云库或强制 TLS 的服务端优先 Require 及以上,不要依赖默认猜测。密码与 URL 可用 secret 文件模式(如 Database__Postgres__UrlFile)注入,勿把真实口令写进公开文档或 git。

端口与监听

容器默认:

LISTEN_IP=0.0.0.0
LISTEN_PORT=7777

只改宿主机端口时改 -p 左侧即可,例如 -p 8096:7777。只有改容器内监听时才同时设 LISTEN_PORT 并改 -p 右侧。普通部署不必手写 ASPNETCORE_URLS;镜像用 ENVIRONMENT / LISTEN_* 生成内部参数。

时区、权限

  • 镜像默认 TZ=UTC;日志本地时间建议显式 TZ=Asia/Shanghai(或所在 IANA 时区)。业务瞬时时间仍按 UTC 存库,与日志展示时区无关。
  • 默认 PUID=1000PGID=1000UMASK=0022,只是普通 Linux fallback。
  • Unraid / 部分 NAS 常见 PUID=99PGID=100;最终以数据目录 owner 为准。
  • entrypoint 不会对已有宿主机目录递归 chown;数据目录不可写则启动失败并提示修正权限。

查看对齐:

id
stat -c '%u:%g %a %n' <data-root> /path/to/media

Unraid host network 示例要点

host network 下不要再 -p;Octans 直接听宿主机 LISTEN_PORT。硬解时映射 render 节点,例如:

docker run -d \
  --name octans \
  --net host \
  --restart unless-stopped \
  -e TZ="Asia/Shanghai" \
  -e Database__Provider="Postgres" \
  -e Database__Postgres__Url="postgresql://octans:<pg-password>@<postgres-host>:5432/octans" \
  -e Database__Postgres__SslMode="Prefer" \
  -e LISTEN_IP="0.0.0.0" \
  -e LISTEN_PORT="7777" \
  -e PUID="99" \
  -e PGID="100" \
  -e UMASK="0022" \
  -v /mnt/cache/appdata/octans:<data-root>:rw \
  --device /dev/dri/renderD128 \
  <registry>/<image>:stable

媒体目录可后挂(如 -v /mnt/user/media:/media:ro);不挂媒体不影响应用起来与设置初始化。Unraid 模板里的 Repository 写镜像引用本身,不要加 https://

Compose 叠加模式

Compose 参考文件位于部署树下的 deploy/docker/octans/(路径随仓库布局,以实际 clone 为准)。思路是:基础 compose + 可选 overlay,env 文件只放本机路径与密钥占位。

SQLite compose

复制 env 示例后至少改:

OCTANS_IMAGE=<registry>/<image>:<tag>
OCTANS_DATA_PATH=<data-root>
OCTANS_MEDIA_PATH=/path/to/media
TZ=Asia/Shanghai
docker compose \
  --env-file deploy/docker/octans/octans.sqlite.env \
  -f deploy/docker/octans/compose.sqlite.yml \
  up -d

PostgreSQL compose(外接库)

compose.postgres.yml 起应用,不起数据库:

OCTANS_IMAGE=<registry>/<image>:<tag>
OCTANS_DATA_PATH=<data-root>
OCTANS_MEDIA_PATH=/path/to/media
TZ=Asia/Shanghai
OCTANS_POSTGRES_URL=postgresql://octans:<pg-password>@<postgres-host>:5432/octans
OCTANS_POSTGRES_SSLMODE=Prefer
docker compose \
  --env-file deploy/docker/octans/octans.postgres.env \
  -f deploy/docker/octans/compose.postgres.yml \
  up -d

叠加参考 PostgreSQL 容器

没有现成库时可叠加 compose.postgres.reference-db.yml(官方 postgres 镜像,仅参考路径):

OCTANS_POSTGRES_PASSWORD=<pg-password>
OCTANS_POSTGRES_DATA_PATH=<data-root>/db/postgres/16
OCTANS_POSTGRES_URL=postgresql://octans:<pg-password>@postgres:5432/octans
OCTANS_POSTGRES_SSLMODE=Disable
docker compose \
  --env-file deploy/docker/octans/octans.postgres.env \
  -f deploy/docker/octans/compose.postgres.yml \
  -f deploy/docker/octans/compose.postgres.reference-db.yml \
  up -d

参考库数据目录由 PostgreSQL 容器用户管理,不归应用 entrypoint 管理。生产更常见的是外接已有 PostgreSQL,而不是依赖参考容器。

nginx overlay(可选)

docker compose \
  --env-file deploy/docker/octans/octans.sqlite.env \
  -f deploy/docker/octans/compose.sqlite.yml \
  -f deploy/docker/octans/compose.nginx.yml \
  up -d

upstream 指向 compose 内 octans:7777。已有 Caddy / Traefik / 宿主机 nginx 时不必叠该文件,只要把流量指到应用 7777,并保留 /hubs/ 的 WebSocket upgrade。内网单机可直接访问 http://<host>:7777

GPU overlay(可选)

无 GPU 时容器仍应启动到 ready;硬件能力不可用 ≠ 部署失败。

docker compose \
  --env-file deploy/docker/octans/octans.sqlite.env \
  -f deploy/docker/octans/compose.sqlite.yml \
  -f deploy/docker/octans/compose.gpu.yml \
  up -d

默认设备:

/dev/dri/renderD128

宿主机节点不同时:

OCTANS_DRI_DEVICE=/dev/dri/renderD129

entrypoint 会按映射后的 render 设备 GID 把运行用户加入附加组,一般不必手填 render 组 GID。硬解链路与 render 节点从宿主机落到容器 / 虚拟机的细节,见系列中的 Unraid SR-IOV 与 FFmpeg 工具链文。

卷、路径与关键环境变量

建议挂载

宿主机路径(示例)容器路径说明
<data-root><data-root>配置、SQLite、日志、secrets、HLS cache、metadata 等持久化根
/path/to/media/media媒体库,建议只读 :ro
参考 PG 数据目录由 PG 容器定义仅 reference-db overlay

应用侧默认路径示例(可被配置覆盖):

SQLite:        <data-root>/db/sqlite3/octans.db
日志:          <data-root>/logs/octans.log
认证密钥:      <data-root>/secrets/…
HLS session:   <data-root>/playback/cache/hls-sessions
metadata:      <data-root>/metadata
FFmpeg:        /opt/octans-ffmpeg/bin/ffmpeg(镜像内)

Compose 辅助变量(节选)

多数 OCTANS_* 只用于 compose 展开,映射进容器 environment 后才变成应用配置。

变量用途备注
OCTANS_IMAGE镜像引用生产写完整 <registry>/<image>:<tag>
OCTANS_DATA_PATH数据目录建议显式
OCTANS_MEDIA_PATH媒体库compose 场景必填
OCTANS_HOST_PORT宿主机端口默认 7777
OCTANS_POSTGRES_URL / SSLMODEPG 连接PG compose 必填
OCTANS_POSTGRES_PASSWORD参考库密码仅 reference-db
OCTANS_DRI_DEVICErender 设备仅 GPU overlay
OCTANS_NGINX_*参考 nginx仅 nginx overlay

容器内常用变量(节选)

变量用途默认 / 要点
TZ日志本地时区默认 UTC,建议显式
PUID / PGID / UMASK运行身份对齐宿主机目录
LISTEN_IP / LISTEN_PORT监听0.0.0.0:7777
ENVIRONMENT运行环境简写默认 Production
Database__ProviderSqlite / Postgres默认 Sqlite
Database__Postgres__UrlPG URLPG 必填
Database__Postgres__SslModeTLS 策略PG 必填
Database__ApplyMigrationsOnStartup启动 migration镜像默认 true
Playback__HardwareAcceleration__RenderDevice硬解设备自定义 GPU 节点时设
刮削 API Key 等Catalog__Providers__…按需,双下划线覆盖配置树

Production 下认证签名密钥为空或仍是开发占位时,后端可自动生成并持久化到数据卷 secrets 目录;显式写入过短或占位密钥会启动失败。普通用户不必在文档里抄真实密钥。

配置优先级

环境变量 > 命令行参数 > 数据库 override > User Secrets(非 Production) > appsettings.{Environment}.json > appsettings.json

含义:Docker -e / compose environment 最高;前端配置中心里若某项 locked,多半已被环境变量钉死。排查:

docker inspect octans --format '{{json .Config.Env}}'
docker logs octans
curl http://127.0.0.1:7777/api/ready

健康检查与启动时序

接口角色
GET /api/readyreadiness:含数据库 / schema 就绪
GET /api/healthliveness:进程与 HTTP 管道可响应,不连库

/api/ready 成功示例形态:

{
  "data": {
    "status": "ready",
    "provider": "Sqlite"
  }
}

镜像 HEALTHCHECK 量级(以当前镜像为准,可按环境调):

interval=10s
timeout=5s
start-period=180s
retries=12

首次启动会在主应用可服务前做 provider 校验与 EF migration。低性能 NAS、机械盘 SQLite、远端 PG 或首次 migration 偏长时,可把 compose 里 start_period 提到 300s。容器 running 但 health 仍 starting 时先看日志,不要只盯 Docker 红点。

网页端「重启服务器」不是 Docker API:应用子进程以约定退出码退出,entrypoint 拉起子进程,容器本身保持运行docker stop / compose stop 的 TERM 走正常停容器路径。

日志、备份与升级

docker logs -f octans
docker exec octans date "+%Y-%m-%d %H:%M:%S %Z %z"

升级前建议备份:

  • 整个数据卷 <data-root>(含 SQLite、secrets、日志与 cache 策略按你的 RPO 取舍)
  • PostgreSQL 模式下的库备份
  • 自定义 env 文件

当前 Web 进程不会在 migration 前自动备份 SQLite。流程应是:停服 / 备份 volume → 换镜像 tag → 启动 → 查 /api/ready 与日志。仅测试库且明确不保留数据时,可走破坏性重建空库再 migration(见 PostgreSQL 专文),生产库勿误用。

常见问题(部署向)

一直 unhealthy

  • PG URL / 密码 / host / SslMode 错误,或版本低于要求。
  • 数据目录对 PUID/PGID 不可写。
  • pending migration 但关掉了启动时 migration。
  • 首次 migration 超过 healthcheck start_period

配置中心改了不生效

环境变量优先级更高;先 docker inspect 环境再查前端 locked 项。

日志时间仍像 UTC

确认容器内 TZdate;若 TZ 对了但 date 仍 UTC,可能是镜像缺 tzdata,需升级镜像或只读挂宿主机 zoneinfo。仅 docker logs --timestamps 前缀为 Z 属于 Docker 驱动行为,与应用日志行内时间无关。

GPU 映射后 FFmpeg / 硬解失败

docker exec octans sh -lc 'ls -l /dev/dri && awk "/^(Uid|Gid|Groups):/ {print}" /proc/1/status'

确认是否叠加 GPU overlay、设备节点是否存在、组权限是否由 entrypoint 补齐。硬解能力验证分层见 FFmpeg 工具链与硬转码系列文。

是否必须用仓库里的 PG / nginx compose

否。正式部署只要求应用能连上 PG 16+(或使用默认 SQLite),以及你自己的入口层能反代到 7777 并处理 WebSocket。

附录:镜像发布通道(公开摘要,非完整 CI)

私有化用户通常只拉取已发布镜像;本节帮助理解 tag 语义,不展开 runner 标签、组织 secrets 与 workflow 文件。

通道谁用推送前大致要求
edge跟踪 main构建 + 容器 smoke(含 SQLite / PG / compose,有 GPU 时含硬解路径)
rc候选验证同上 + 不可变 fixed/trace tag + 发布说明资产
stable生产默认移动标签同上;fixed tag 与 stable digest 应对齐

公开原则:

  1. fixed tag 与 trace tag 不可变;已存在则拒绝覆盖,修 bug 发下一版号。
  2. 发布后应对齐 fixed / trace / moving tag 的 digest,避免「标签指到不同层」。
  3. 稳定版默认文档入口用 stable;钉版本用 fixed tag。
  4. 对外文档里的 registry 用 <registry>/<image>:<tag> 占位;内网真实主机名、机器人 token、密码不要写进公开文。
  5. 脱离 CI 的手工推送属于变更控制流程,不在本文展开。

本机拉取与对照(示例):

docker pull <registry>/<image>:stable
docker buildx imagetools inspect <registry>/<image>:stable

边界与注意

  1. 本文是私有化部署与运行形态说明,不是 Kubernetes 方案,也不是 PostgreSQL / 网关完整运维手册。
  2. 镜像地址、密码、API Key 一律占位;生产用 secret 管理,勿提交 env 真值。
  3. /dev/dri/renderD*、数据路径、端口以本机为准;硬解依赖驱动与 FFmpeg runtime,见相关阅读。
  4. 升级前备份 volume;migration 不可逆操作前先确认 RPO。
  5. 配置优先级记住「环境变量压过配置中心」,排障先 inspect 再改 UI。

相关阅读

同系列还可对照:

来源

本文综合改写自 Octans 项目 usage 层 Docker 部署说明与 Docker 镜像 CI 发布说明中的部署与通道口径。镜像仓库主机名、digest、secrets 与 workflow 细节已脱敏为占位或省略;Compose 文件名与环境变量键名保留可操作语义,路径与版本以目标环境验证为准。