文章目录
- 📖 介绍 📖
- 🖥️ 演示环境 🖥️
- 📒 权限这道关卡到底卡在哪一关 📒
- 🔍 先认准报错卡在哪一关
- 🧩 UID 和 GID 这两个数字在管什么
- 🧪 三步定位:到底是谁不让写
- 🛠️ 三种处置,按场景挑
- ✅ 改完怎么验证真的通了
- ⚓️ 相关链接 ⚓️
📖 介绍 📖
在 NAS 上用 Docker 跑服务,最让人摸不着头脑的报错之一,不是镜像拉不下来,也不是端口冲突,而是明明目录已经挂进去了、
docker ps也显示容器在跑,日志里却反复刷Permission denied——应用写不进共享目录,轻则配置存不住,重则容器起不来反复重启。奇怪的是,你ls宿主目录明明看得到、权限还是777,怎么就是不让写。这类问题卡住过太多人,根子几乎都不在 Docker,也不在 NAS 本身,而是在「容器里的进程到底以谁的名义去碰那块盘」。这篇文章把这道关卡的账一步步算清楚:先认准报错卡在哪一关,再讲透 UID 和 GID 这两个数字是怎么决定「能不能写」的,然后给一套能照着抄的定位命令和三种处置方案,最后说清楚 SMB 和 NFS 挂载还会额外多一道墙。
🖥️ 演示环境 🖥️
- 🐧 系统:飞牛 fnOS
- 🟢 运行时:Docker 27
- 🌐 网络:内网
📢注意:本文内容为个人学习笔记,仅供参考。附:读者须知
📒 权限这道关卡到底卡在哪一关 📒
🔍 先认准报错卡在哪一关
Permission denied只说明一件事:内核拒绝了这一次写操作。它不区分「目录不存在」「盘满了」「挂载没生效」「还是真没权限」。所以第一步不是急着改权限,而是把前几种可能先排除掉。
最朴素的复现命令,进容器里直接试:
# 把容器里挂载点换成你自己的路径dockerexec-it你的容器名touch/data/test_write_$(date+%s)- 报
No such file or directory:多半是挂载路径写错,或者共享目录根本没挂进来,跟权限无关。 - 报
Read-only file system:挂载本身是只读的,先去查挂载选项和 NAS 侧是否锁了只读。 - 报
Permission denied:这才真正进入权限这道题。
把这一关认准,后面才不会在错误的方向上浪费时间。
🧩 UID 和 GID 这两个数字在管什么
很多人以为「我用 root 跑的容器,怎么可能没权限」。问题恰恰在这:容器里的 root 和 NAS 上的 root 不是同一个账本。
Linux 管文件权限看的是数字编号,不是用户名。每个文件都记着「属主 UID」和「属组 GID」两个数字;每个进程启动时也带着一个数字身份(有效 UID/GID)。写文件能不能成功,比的是「进程这个数字身份」对「目标目录这个数字属主」有没有写权限。
容器里的情况要分两层看:
- 镜像里的应用以什么身份跑:很多规范镜像(尤其是 LinuxServer.io 一系)默认不让应用用 root 跑,而是内置一个固定非特权用户,比如 UID
1000。你在宿主ls看到777也未必管用:一来你看到的目录未必就是容器真正在写的那个(挂载映射可能错位),二来挂载层、远端 ACL 还可能另有一套判定,777根本传不到那一层。 - 共享目录在 NAS 侧是谁的:飞牛这类 NAS 的共享目录,底层文件其实归属 NAS 系统里的一个用户身份。你从宿主机
ls -n看到的数字 UID,才是 Linux 内核真正拿来比对的那个数。
所以「容器里是 root、宿主目录我也能看」和「容器进程能写进去」之间,隔着的正是这两个数字对没对上。
🧪 三步定位:到底是谁不让写
不用猜,三条命令把账算明白。
# 1) 看容器里进程到底以哪个数字身份在跑dockerexec你的容器名id# 输出里看 uid= 和 gid= 那两个数# 2) 看宿主共享目录在 Linux 这层归属谁(数字形式)ls-n/你的/宿主/共享路径# 第一列第三、四字段就是属主 UID 和属组 GID# 3) 进容器看挂载点里看到的数字属主,和宿主是否一致dockerexec你的容器名ls-n/data判据很清楚:
- 容器内进程 UID/GID 与挂载点目录属主 UID/GID 不一致,且目录没有对「其他用户」开放写位 → 写被拒是必然。
- 两边数字一致 → 那就要往更上层查(SMB/NFS 挂载的远端 ACL、挂载只读、或者 NAS 侧又套了一层权限)。
这套三步法能覆盖绝大多数场景,比盲目chmod 777靠谱得多——而且你会发现,很多时候777也救不了,原因就在下面这一关。
🛠️ 三种处置,按场景挑
场景一:镜像支持PUID/PGID环境变量(最常见)
这类镜像启动时会用这两个变量把应用「降权」到你指定的数字身份。处置最简单:把宿主目录属主的数字 UID/GID 填进去,让它和应用身份重合。
services:your_app:image:某个支持PUID/PGID的镜像environment:-PUID=1000# 换成 ls -n 看到的属主 UID-PGID=1000# 换成 ls -n 看到的属组 GIDvolumes:-/你的/宿主/共享路径:/data场景二:镜像不支持降权,或应用硬跑在某个固定 UID
可以两条路:一是把宿主共享目录的属主chown成容器应用用的那个 UID/GID,让两边数字对齐;二是在 compose 里用user:直接指定容器进程跑在某个 UID(注意:这样应用新建的配置文件也会归这个 UID,要和 NAS 侧能识别的一致)。无论哪条,原则都是「让进程的数字身份和目标目录的数字属主对上」。
场景三:共享目录走的是 SMB / NFS 挂载(额外一道墙)
这是最容易踩、也最容易被777误导的情况。SMB/NFS 挂载在远端,远端有自己的一套账号和 ACL:
- NFS 默认开
root_squash,容器里就算以0(root) 跑,落到远端也会被压成nobody,照样写不进。 - SMB 挂载通常会把远端所有文件映射成客户端指定的某一个 UID,NAS 侧再按自己的权限判定。
处置办法是让挂载选项和容器的数字身份配合:要么把PUID/PGID设成 NAS 侧那个真正有写权限的用户身份,要么在挂载时把远端用户映射成本地能写的那组数字。先把上面的「三步定位」跑一遍,确认卡在远端这一层,再动挂载参数,别在容器里反复改权限却始终够不着远端的锁。
✅ 改完怎么验证真的通了
处置后别只看日志不报错就收工,用最直白的方式确认一次:
# 进容器在挂载点写一个文件dockerexec你的容器名touch/data/verify_$(date+%s)# 回宿主(或 NAS 文件管理器)看这个文件是否真的生成、属主是否正确ls-n/你的/宿主/共享路径文件能在 NAS 共享目录里出现、且属主数字符合预期,这一关才算真正过了。很多「改完好像好了」其实是应用换了个能写的位置继续跑,共享目录里依旧空的,按这条命令一验就现形。
📌注意事项:
- 改宿主共享目录属主前,先确认目录里没有别的容器或应用在用,避免把别人的读写权限一并改掉。
- 通过
user:或PUID/PGID改身份后,历史上已有的配置文件属主可能不对,必要时一并chown,否则老配置读不出。- 动 SMB/NFS 挂载参数属于改动存储访问方式,建议低峰操作,改动前对共享目录做一份备份,避免映射错位导致看不到原有文件。
⚠️说明:本文基于该时段主流容器镜像与飞牛 fnOS、Docker 的常规机制整理。开源镜像的内置用户设定、NAS 系统的共享权限模型后续都可能与文中描述不同,请前往对应项目的官方文档与你的 NAS 系统说明查看最新内容并自行核实,一切以官方当前版本为准;本文所述仅代表整理时点的实操经验,仅供参考。
⚓️ 相关链接 ⚓️
- XiaoqiangClub
- Docker Compose 里的 PUID 和 PGID 到底是什么?
- 飞牛 NAS 新手必看!超详细开启 & 连接 SSH 与获取 root 权限教程
- LinuxServer.io 镜像的 PUID/PGID 说明
- 飞牛 fnOS 官方文档