跳转到内容

写好 compose 文件:卷、端口、环境变量与网络 ​

读完这篇,你能看懂插件示例里每一行的意思,知道文件在磁盘上长什么样,写对数据卷和端口,并且不会和 OMV 自己占用的端口撞车。

开始之前 ​

  • 已按部署第一个应用走通过一遍。
  • compose 文件是 YAML:缩进只能用空格(通常两个),不能用 Tab,缩进错了容器就起不来。求助时把文件放进代码块里贴出来,并先去掉密码、邮箱和域名。
  • 插件示例文件开头有 # Date: 注释和官方链接。示例可能落后于应用官方的写法,部署前对照一遍。

文件怎么落盘 ​

你在 服务 → Compose → 文件(Services → Compose → Files)里保存的每个应用,插件会在 Compose 文件 共享文件夹里生成这些东西:

text
<appdata 共享文件夹>/
├── global.env              全局环境变量文件(启用时),传给所有应用
└── <名称>/
    ├── <名称>.yml           compose 文件
    ├── compose.yml          指向 <名称>.yml 的符号链接(默认创建)
    ├── <名称>.env           环境变量文件
    ├── .env                 指向 <名称>.env 的符号链接(默认创建)
    └── compose.override.yml 覆盖文件(在编辑页勾选「显示覆盖」填写)
  • 这些文件都是插件生成的,开头写着「Do not edit this file, your changes will get lost」。改动一律在界面里做;在 shell 里手改了,要回到文件列表点 从文件同步变更(Sync changes from file)。
  • 新建或编辑 compose 文件,保存时插件会马上写盘,不需要点黄色提示条上的应用。删除文件,以及改设置页、计划任务、全局环境文件之后,才会出现提示条,需要点应用。
  • 应用的数据目录最好放在 <名称>/ 的子目录里,或者别的共享文件夹里,不要把整个 <名称>/ 目录当数据卷,原因见下面「卷」一节。

在 OMV 的 shell 里(root)可以用 omv-compose-run 对某个应用执行 docker compose,它会带上和界面完全一样的文件和参数:

sh
omv-compose-run uptime-kuma ps
omv-compose-run uptime-kuma logs --tail 100
omv-compose-run -n uptime-kuma config   # 只打印会执行的完整命令,不执行

占位符 ​

插件在保存文件时,会把几种占位符替换成真实的值。它们可以写在 compose 文件、env 文件和覆盖文件里:

text
CHANGE_TO_COMPOSE_DATA_PATH   设置页「数据」共享文件夹的绝对路径
CHANGE_TO_COMPOSE_NAME        当前 compose 文件的名称
${{ sf:"共享文件夹名" }}       该共享文件夹的绝对路径(8.0.4 起)
${{ uid:"用户名" }}            该用户的 UID(8.0.5 起)
${{ gid:"用户组名" }}          该用户组的 GID(8.0.5 起)
${{ tz }}                     系统时区,例如 Asia/Tokyo(8.0.5 起)
${{ nfp: 18080 }}             18080 起第一个空闲的主机端口,保存时确定并锁定(8.1.14 起)

一个用了占位符的例子:

yaml
services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    environment:
      - PUID=${{ uid:"appuser" }}
      - PGID=${{ gid:"users" }}
      - TZ=${{ tz }}
    volumes:
      - ${{ sf:"appdata" }}/jellyfin/config:/config
      - ${{ sf:"media" }}:/media:ro
    ports:
      - "${{ nfp: 8096 }}:8096"

OMV 8 之后,用 sf、uid、gid、tz 就够了,旧教程里靠 global.env 存 PUID 之类变量的做法不再必须,wiki 也这么说。占位符里的用户、用户组、共享文件夹必须真实存在,名字拼错的话,占位符会原样留在文件里。

卷:数据放哪、怎么写 ​

写法例子说明
绑定挂载(bind mount)/srv/dev-disk-by-uuid-<UUID>/appdata/x:/config把 NAS 上的一个目录接进容器。推荐,你看得见、备份得到
相对路径./config:/config相对于这个应用自己的目录,也算绑定挂载。插件备份默认包含
命名卷(named volume)mydata:/config由 Docker 管理,放在 Docker 存储里。备份和迁移都更麻烦,除非应用官方要求,一般不用

几条规则:

  1. 挂载子目录,不要挂载应用目录本身。 写 appdata/jellyfin/config:/config,不要写 appdata/jellyfin:/config。后者让容器直接写进插件管理的目录,Docker 可能改掉它的权限,把容器或插件弄坏。
  2. 只读的媒体库加 :ro,也就是把挂载写成「media 共享文件夹的路径:/media:ro」的样子,上面的示例里就有这一行。容器被攻破时,它删不掉你的片库。
  3. Docker 自动创建的目录属于 root。容器用别的用户运行时,要先建好目录并设好权限,见容器权限:PUID、PGID 与共享文件夹。服务 → Compose → 卷(Volumes)页的 创建绑定挂载路径(Create bind mount path)可以建目录并设置拥有者、用户组和权限。
  4. 备份会包含哪些卷,见更新与备份容器。卷行尾加 # BACKUP 强制纳入,加 # SKIP_BACKUP 强制排除。
  5. 用符号链接做卷路径可以,但 Docker 存储本身不要用符号链接。符号链接插件见挂载编辑器与符号链接。

环境变量:三种写法 ​

位置写法用途
compose 文件里的 environmentKEY: 值直接传给容器
<名称>.env(编辑页勾选 显示环境文件,在 环境 框里写)KEY=值给 compose 文件里的 ${KEY} 做替换,不会自动进入容器
global.env(文件列表工具栏的 编辑全局环境文件)KEY=值对所有应用生效。OMV 8 里基本不需要

容易搞混的一点:.env 里的变量默认只用来替换 compose 文件里的 ${KEY}。想让它进容器,要么在 environment 里写 KEY: ${KEY},要么用 env_file: - .env。后一种依赖设置页里默认开启的 创建符号链接,因为它读的是目录里的 .env 链接。Immich 的官方 compose 就是这么写的。

美元符号要小心。compose 文件里的 $ 会被当成变量替换,要写字面的 $,在 compose 文件里写成 $$;在 .env 文件里则把整个值放进单引号,里面的 $ 保持单个。密码里带 $ 时尤其要注意,Vaultwarden 的管理令牌就是个典型例子。

端口 ​

端口映射的格式是 "NAS 上的端口:容器里的端口":

yaml
ports:
  - "8096:8096"          # 对局域网所有设备开放
  - "127.0.0.1:8001:80"  # 只有 NAS 自己能访问
  - "6881:6881/udp"      # UDP
  • 端口写成带引号的字符串,这是 Compose 官方规范的建议,避免 YAML 把 xx:yy 当成六十进制数。
  • 不写 IP 就是绑定所有网卡。官方文档明确提醒,这样发布的端口会绕过宿主机防火墙。
  • 用了 network_mode: host 的容器不能再写 ports,它直接用宿主机的网络。

映射出去的端口,OMV 的防火墙规则可能管不到

Docker 官方文档说明:发布的端口走 nat 表,数据包在到达 INPUT 链之前就被转走,所以 ufw 之类基于 INPUT 的规则会被绕过。OMV 的防火墙脚本也只管理 INPUT 和 OUTPUT 链(源码),所以 网络 → 防火墙(Network → Firewall)里写的规则对映射端口很可能不起作用。这一点还没有在真机上验证。

要限制来源,可以把端口绑到具体地址(如 127.0.0.1:端口),或者按 Docker 文档的办法在 DOCKER-USER 链上写规则。不要指望界面里的防火墙替你挡住它。

先对端口,再上线 ​

OMV 自己和已启用的插件占着一批端口,映射到同一个端口,容器会起不来:

端口谁在用
80OMV Web 界面(系统 → 工作台 里的 端口)
443启用 HTTPS 后的 OMV Web 界面(默认 443)
22SSH
445、139SMB/CIFS
2049NFS
53omv-extras wiki 指出 OMV 自己占着 53,Pi-hole 这类要占 53 的容器会冲突
3670、2342、2222、3689官方 FileBrowser、PhotoPrism、WeTTY、OwnTone 插件的默认端口(启用后)
5000容器终端插件 CTerm 的默认端口(启用后)
8080、8443Kubernetes 插件的负载均衡默认端口(启用后)

应用不要映射 80 和 443。 需要这两个端口的应用(反向代理),先把 OMV 的 Web 界面端口改掉,见反向代理与 HTTPS。

插件示例里的默认端口,有几个会直接撞车:

应用示例里的端口注意
Vaultwarden80撞 OMV
Nextcloud(LinuxServer 示例)443启用了 OMV 的 HTTPS 就撞
Nginx Proxy Manager80、443、81要先改 OMV 的端口
qBittorrent、Traefik 示例8080两个同时跑会互撞,Kubernetes 插件也用 8080
Duplicati(wiki 里的示例)8200和 MiniDLNA 插件的默认端口相同

怎么查端口有没有被占:

  • 在 OMV 的 shell 里(root)运行 ss -tulpn,列出所有监听中的 TCP 和 UDP 端口以及占用它的进程。
  • 服务 → Compose → 端口(Ports)页汇总所有 compose 应用映射的主机端口,建议空闲端口(Suggest free ports)可以列出某个起点之后的 20 个空闲端口。它只知道 compose 应用,不知道 OMV 自己的服务,所以仍要用 ss 核对。
  • 上面的 nfp 占位符,保存时自动挑一个空闲端口并锁定。

端口冲突的排查见端口冲突。

网络 ​

方式写法适合
默认网络不写同一个 compose 文件里的容器,用服务名互相访问,例如数据库主机填 db
共享网络外部网络不同 compose 文件里的容器要互相访问,例如反向代理访问应用
network_mode: host容器直接用宿主机网络需要局域网广播发现的应用,例如 Syncthing。端口冲突要自己管,Docker 不会为它创建防火墙规则
macvlan 或 ipvlan让容器在局域网里有独立 IP需要占 53 端口的 Pi-hole 等

不同 compose 文件的容器默认互相隔离。要让它们互通,先在 服务 → Compose → 网络(Networks)页点 创建(Create),建一个名叫 proxy 的 bridge 网络,再在两边的 compose 文件里都加上:

yaml
services:
  app:
    networks:
      - default
      - proxy
networks:
  proxy:
    external: true

default 这一行不能漏:服务一旦写了 networks,就只加入列出的网络。只有一个服务的应用可以只写 proxy;文件里还有数据库之类的容器时,要保留 default,不然这个服务就连不上它们。

macvlan 网络可以在同一个页面创建,但有两点要小心。一是宿主机默认访问不到 macvlan 上的容器。二是 omv-extras wiki 给的解决办法要手写网络配置文件,而 OMV 8 用 netplan 管网络,wiki 自己也警告可能冲突、风险自负,本站不教。

重启策略和镜像标签 ​

  • restart: unless-stopped:容器退出就重启,Docker 随 NAS 启动时也一起启动,除非你手动停止了它。插件示例都用它。always 与它的区别是,手动停止的容器在 Docker 重启后会被再次拉起。
  • 镜像标签:latest 指向最新稳定版,会跨大版本。数据库、相册、文档类应用升级大版本可能要做数据迁移,这类应用用应用官方推荐的大版本标签,更新前看发布说明,见更新与备份容器。
  • compose 文件里不要再写顶层的 version:,Compose v2 会提示它已过时。

检查结果 ​

  1. 在文件列表选中应用,点 检查(check)。没有红色报错,说明语法对。
  2. 想确认占位符和变量最终变成了什么,在 OMV 的 shell 里(root)运行 omv-compose-run <名称> config,它会打印展开后的完整配置。输出里可能有密码,不要原样贴到公开的地方。
  3. 上线后,在 服务 → Compose → 端口 页能看到这个应用的端口,浏览器能打开。

常见问题 ​

保存或检查时报 YAML 语法错误。 先看缩进是不是用了 Tab,冒号后面有没有空格。字符串里有冒号或 # 时加引号。

日志或检查结果里出现 The "xxx" variable is not set. Defaulting to a blank string。 compose 文件里有 $xxx 被当成变量。要字面的 $,写成 $$,见上面的环境变量一节。

我在 shell 里改了 .yml,界面里没变,过一会儿又被改回去了。 界面是以 OMV 数据库里保存的内容为准,磁盘上的文件会被重新生成。改完磁盘文件后,点 从文件同步变更,或者直接在界面里编辑。

占位符没有被替换,文件里还留着 sf 之类的字样。 检查共享文件夹、用户和用户组的名字是否完全一致,以及设置页的 数据 是否选了共享文件夹。

想在不改示例的前提下调整端口或加变量。 编辑页勾选 显示覆盖,在 覆盖 框里写一份覆盖文件(compose.override.yml),它会和主文件合并。

参考资料 ​

OpenMediaVault 非官方中文使用指南,与 openmediavault 项目及其开发者无隶属关系。