外观
容器权限:PUID、PGID 与共享文件夹
完成后,你的容器以一个专用用户运行,不是 root,也不是 admin。它能写该写的目录,别的用户(比如通过 SMB 访问的家人)也能正常读写它生成的文件。
先理解三件事
- 容器的权限,就是它的 UID 和 GID 的权限。 容器和 NAS 共用内核,Linux 只认数字 ID。容器里以 UID 1000 运行的进程,在 NAS 上就是 UID 1000 的用户,不管它在容器里叫什么名字。omv-extras wiki 把这个原则叫作 Docker 的安全模型。
PUID和PGID是 LinuxServer 系镜像的约定。 这类镜像(lscr.io/linuxserver/…)用这两个环境变量指定应用以哪个用户和组运行。官方镜像大多改用 compose 的user:字段,有些镜像(比如数据库)必须先以 root 启动,再自己降权。用哪种写法,看应用官方文档。- OMV 界面里的「共享文件夹权限」管不到容器。 容器直接读写文件系统,只看文件的属主、属组和权限位。OMV 里给用户设置的 共享文件夹权限(Shared folder permissions),在 OMV 核心源码里只有 SMB 和 rsync 服务会读取它,对容器没有任何作用。三层权限的关系见三层权限:共享权限、ACL 与文件系统权限。
OMV 的默认约定是:界面里创建的用户,主组都是 users(GID 100);共享文件夹默认属主是 root、属组是 users,属组有读写权限,并带 setgid,新文件会继承 users 组。所以只要容器的 GID 是 100,它写出来的文件,SMB 用户通常就能碰到。
做法:建一个专用用户
omv-extras wiki 的建议:建一个专用用户,比如 appuser,用它跑所有容器。这对绝大多数家庭用户已经够用。
- 打开
用户 → 用户(Users → Users),点 新建 | 导入(Create | Import)里的 创建(Create)。 - 名称填
appuser,密码设一个强密码,确认密码。这个用户不用来登录,但表单要求必填。 - 用户组保持默认的
users。需要 Intel 核显转码时,再加上render和video。 - 不要加入
docker、sudo、_ssh组。docker组的成员等价于 root。 - 保存。页面顶部出现黄色提示条的话,点应用。
然后查它的 UID 和 GID:用户 → 用户 页默认隐藏了 UID 和 GID 两列,点表格右上角的列选择图标把它们打开。也可以在 OMV 的 shell 里(root)运行 id appuser。
在 compose 文件里不用手抄数字,用占位符让插件去读:
yaml
environment:
- PUID=${{ uid:"appuser" }}
- PGID=${{ gid:"users" }}
- UMASK=0021
2
3
4
2
3
4
永远不要把容器映射成 root 或 admin
PUID=0 是 root,OMV 的 Web 管理员 admin(wiki 写的 UID 是 998)也一样,两者都等于把整台 NAS 交给容器。容器里的程序有漏洞时,攻击者就拿到了这个身份。确实需要 root 的容器,先确认镜像来源可信,并且只挂载它必须用的目录。
UMASK=002 是 LinuxServer 系镜像提供的可选变量,让容器新建的文件是 664、目录是 775,同组的 SMB 用户才能修改它们,默认的 022 只给属组读权限。umask 是在默认权限上做减法,不是 chmod,用之前读一下 LinuxServer 文档里关于 umask 的说明。
给容器哪些目录
容器只能看到你挂载进去的目录。 这才是限制容器权限最有效的手段,比 UID 更直接:
- 只挂载应用必须用的目录,不要图省事把整块盘挂进去。
- 只需要读的目录加
:ro,比如媒体库。 - 配置目录放在
appdata下按应用分的子目录里,不要把整个appdata挂进容器。
目录属主的原则,LinuxServer 的文档是这么说的:确保宿主机上被挂载的目录,属于你指定的那个用户,权限问题就会消失。 目录不存在时,Docker 会以 root 身份创建它,容器里的非 root 用户可能就写不进去。所以提前建好:在 服务 → Compose → 卷(Volumes)页点 创建绑定挂载路径(Create bind mount path),填共享文件夹加相对路径,设好拥有者、用户组和权限。它会拒绝在关键系统路径上操作。递归应用所有权和权限(Apply ownership and permissions recursively)这个选项默认关着,对已经有数据的目录要慎用。
不要为了让报错消失就把目录改成 777。这等于取消了所有保护,下面的排错表能找到真正的原因。
想要更强的隔离
appuser 用在所有容器上,意味着每个容器都能碰到所有挂载给 appuser 的目录。如果一个应用要暴露到外网,让它用独立的用户和组。wiki 的做法是在命令行建(界面建的用户主组都是 users):在 OMV 的 shell 里以 root 身份运行 useradd -U <名称>,同时创建用户和同名的组,回界面就能看到它,之后把需要的用户加进这个组。这一步没有在真机上验证。
几个会得到 root 的陷阱
| 做法 | 为什么危险 |
|---|---|
把 /var/run/docker.sock 挂载进容器 | OWASP 的说法是,能访问它就等于拿到宿主机不受限的 root 权限。加 :ro 也不行,它不会让 Docker 接口变成只读 |
privileged: true | 容器拿到全部内核能力,几乎等于宿主机 |
加入 docker 组的用户 | 等价于 root |
| 容器以 root 运行并挂载共享文件夹 | 它建的文件属于 root,SMB 用户改不了;它被攻破时,挂载进去的目录都能被改 |
Homepage、Dozzle、Watchtower 这类要读 Docker 状态的应用都需要 docker.sock。能不挂就不挂;必须用时,Homepage 的官方文档推荐用 docker-socket-proxy 只开放只读接口,见导航页与可用性监控。
检查结果
用户 → 用户里能看到appuser,用户组里有users,没有docker。- 容器起来后,在 OMV 的 shell 里(root)看数据目录的属主:
ls -ln <数据目录>。属主 UID 应当等于appuser的 UID,属组应当是 100。 - 在 SMB 客户端里打开这个共享文件夹,能读到容器生成的文件,该改的文件能改。
- 容器日志里没有
Permission denied。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
日志里 Permission denied 写 /config | 目录由 Docker 以 root 创建,容器用户写不进去 | 用创建绑定挂载路径建好目录并设好拥有者,或者单独 chown 这一个目录,不要递归改共享文件夹 |
| 容器能写,但 SMB 用户改不了它建的文件 | 文件权限是 644,属组只有读权限 | 加 UMASK=002,已有文件在 SMB 客户端上重新保存或用 resetperms 插件 恢复标准权限,重置前先读它的警告 |
| 容器看不到媒体文件 | 路径没挂载对,或 appuser 对那个目录没有读权限 | 先在 卷 页核对挂载,再用 ls -ln 看权限。媒体目录至少要让 users 组可读 |
| 改了共享文件夹权限后容器还是读不了 | 改的是 SMB 层的权限,对容器没用 | 要改的是文件系统权限,见设置共享文件夹权限 |
PUID 写了但文件还是 root 的 | 镜像不是 LinuxServer 系,不认这两个变量 | 看镜像官方文档,改用 user: 字段 |
给容器专用的目录怎么规划,也可以参考常见权限方案。