在群晖 NAS 上部署 Docker 容器时,经常会遇到 `Permission denied`(权限不足)。一个常见原因是:**容器里的运行用户与群晖宿主机挂载目录的所有者不一致**。 例如,有些镜像默认使用 UID `1000`,而群晖中你创建的文件夹属于另一个 UID。即使该群晖账号是管理员,容器内的普通用户也不会自动获得文件夹写入权限。 下面这条命令能快速列出所有容器的**镜像默认用户、容器配置用户、UID、GID 和运行状态**,包括已经停止的容器。 ## 一、一键复制执行 先通过 SSH 登录群晖,使用可以执行 `docker` 命令的账号。**复制下面整行命令,粘贴到终端并回车即可**,不需要创建 `.sh` 文件。 ```bash printf '容器名称 | 镜像用户 | 容器用户 | UID | GID | 状态\n'; printf '%s\n' '--------------------------------------------------------------------------------'; docker ps -aq | while IFS= read -r c; do n=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null) || continue; im=$(docker inspect -f '{{.Image}}' "$c"); iu=$(docker image inspect -f '{{.Config.User}}' "$im" 2>/dev/null); u=$(docker inspect -f '{{.Config.User}}' "$c"); s=$(docker inspect -f '{{.State.Running}}' "$c"); uid=-; gid=-; if [ "$s" = true ]; then info=$(docker exec "$c" id 2>/dev/null); uid=$(printf '%s\n' "$info" | sed -n 's/.*uid=\([0-9][0-9]*\).*/\1/p'); gid=$(printf '%s\n' "$info" | sed -n 's/.*gid=\([0-9][0-9]*\).*/\1/p'); st=运行中; else st=已停止; case "$u" in ''|root|0|0:0) uid=0; gid=0;; *:*) a=${u%%:*}; b=${u#*:}; case "$a" in ''|*[!0-9]*) ;; *) uid=$a;; esac; case "$b" in ''|*[!0-9]*) ;; *) gid=$b;; esac;; *) case "$u" in *[!0-9]*) ;; *) uid=$u;; esac;; esac; fi; printf '%-21.21s | %-14.14s | %-14.14s | %5s | %5s | %s\n' "${n#/}" "${iu:-root}" "${u:-root}" "${uid:--}" "${gid:--}" "$st"; done ``` 命令为**只读查询**:不会启动已停止的业务容器,不会修改容器、目录或权限。对于正在运行的容器,只会执行一次 `id` 查询。 ## 二、输出示例 以下仅为**格式示例**,不是任何 NAS 的实时结果: ```text 容器名称 | 镜像用户 | 容器用户 | UID | GID | 状态 -------------------------------------------------------------------------------- RustDeskGen | user | 1000:1000 | 1000 | 1000 | 运行中 Mysql | root | 1026:100 | 1026 | 100 | 运行中 Redis | root | root | 0 | 0 | 运行中 OpenList | openlist | 1026:100 | 1026 | 100 | 已停止 ``` | 字段 | 含义 | | --- | --- | | 容器名称 | Docker 容器名 | | 镜像用户 | 镜像声明的默认启动用户,例如 `user`、`redis`、`root` | | 容器用户 | 创建容器时采用的用户,可能由 Compose 的 `user:` 或运行参数 `--user` 覆盖 | | UID / GID | 运行中的容器:`docker exec id` 的数字身份;已停止的容器:仅解析可确定的数字用户配置 | | 状态 | `运行中` 或 `已停止` | **注意:** 如果停止容器配置的是 `user` 等*用户名*,脚本可能显示 `-`,因为它不启动镜像去解析 `/etc/passwd`;这是有意保留的不确定性,而不是查询失败。 ## 三、为什么镜像用户和容器用户会不同? 例如镜像默认用户为 `user`,但你在 Compose 中加入: ```yaml services: app: image: example/app:latest user: "1026:100" ``` 那么容器可能按 `1026:100` 运行,而非镜像默认身份。**强行统一用户并不一定安全或兼容**:有的应用依赖固定的 HOME 目录、Python 用户级安装路径、初始化权限等,修改后可能无法启动。 更推荐的做法是:**保持镜像推荐的运行用户,再针对它专用的宿主机数据目录授予适当读写权限**。 ## 四、排查目录权限时,再搭配两条命令 **1. 查看某个容器实际运行的进程用户:** ```bash docker top RustDeskGen ``` 镜像可能以 root 启动,然后把业务进程切换到其他用户;所以本篇表格中的 UID/GID **不一定等于所有业务进程的身份**。如果要调整数据库等敏感目录的权限,一定要核对实际应用进程。 **2. 查看容器挂载了哪些群晖文件夹:** ```bash docker inspect RustDeskGen --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' ``` 结合容器实际身份、挂载目录及群晖 ACL,判断哪个目录需要授权。不要仅凭表格就批量修改所有文件夹的所有者。 ## 五、常见问题 **Q:为什么显示 `root`,但应用目录仍然报权限错误?** A:`root` 可能只是容器的启动身份。镜像入口脚本可能再切换到 `mysql`、`redis` 等用户;宿主机还可能设置了 ACL 或只读挂载。 **Q:为什么停止的容器 UID/GID 是 `-`?** A:脚本不启动已停止容器,也不猜测镜像内用户名对应的数字 UID/GID;只有明确的数字配置才会直接展示。 **Q:为什么不直接给整个 `/volume1/docker` 设置 Everyone 读写?** A:该目录可能包含数据库、密钥和配置文件。为了排除权限错误而全面开放读写,会扩大安全风险。建议仅授权应用真正需要写入的目录。 **Q:表格出现轻微错位怎么办?** A:请用等宽字体查看 SSH 输出,并适当加宽终端。中文字符在不同终端的显示宽度可能不同;命令用 `|` 分隔列,确保即使视觉上略有偏差也能读清数据。 --- **总结:** 查询容器用户时,先分清「镜像默认用户」「容器配置用户」和「业务进程用户」三个概念,再针对数据挂载目录设置权限,比强制所有 Docker 容器使用同一个用户更稳妥。 Loading... 在群晖 NAS 上部署 Docker 容器时,经常会遇到 `Permission denied`(权限不足)。一个常见原因是:**容器里的运行用户与群晖宿主机挂载目录的所有者不一致**。 例如,有些镜像默认使用 UID `1000`,而群晖中你创建的文件夹属于另一个 UID。即使该群晖账号是管理员,容器内的普通用户也不会自动获得文件夹写入权限。 下面这条命令能快速列出所有容器的**镜像默认用户、容器配置用户、UID、GID 和运行状态**,包括已经停止的容器。 ## 一、一键复制执行 先通过 SSH 登录群晖,使用可以执行 `docker` 命令的账号。**复制下面整行命令,粘贴到终端并回车即可**,不需要创建 `.sh` 文件。 ```bash printf '容器名称 | 镜像用户 | 容器用户 | UID | GID | 状态\n'; printf '%s\n' '--------------------------------------------------------------------------------'; docker ps -aq | while IFS= read -r c; do n=$(docker inspect -f '{{.Name}}' "$c" 2>/dev/null) || continue; im=$(docker inspect -f '{{.Image}}' "$c"); iu=$(docker image inspect -f '{{.Config.User}}' "$im" 2>/dev/null); u=$(docker inspect -f '{{.Config.User}}' "$c"); s=$(docker inspect -f '{{.State.Running}}' "$c"); uid=-; gid=-; if [ "$s" = true ]; then info=$(docker exec "$c" id 2>/dev/null); uid=$(printf '%s\n' "$info" | sed -n 's/.*uid=\([0-9][0-9]*\).*/\1/p'); gid=$(printf '%s\n' "$info" | sed -n 's/.*gid=\([0-9][0-9]*\).*/\1/p'); st=运行中; else st=已停止; case "$u" in ''|root|0|0:0) uid=0; gid=0;; *:*) a=${u%%:*}; b=${u#*:}; case "$a" in ''|*[!0-9]*) ;; *) uid=$a;; esac; case "$b" in ''|*[!0-9]*) ;; *) gid=$b;; esac;; *) case "$u" in *[!0-9]*) ;; *) uid=$u;; esac;; esac; fi; printf '%-21.21s | %-14.14s | %-14.14s | %5s | %5s | %s\n' "${n#/}" "${iu:-root}" "${u:-root}" "${uid:--}" "${gid:--}" "$st"; done ``` 命令为**只读查询**:不会启动已停止的业务容器,不会修改容器、目录或权限。对于正在运行的容器,只会执行一次 `id` 查询。 ## 二、输出示例 以下仅为**格式示例**,不是任何 NAS 的实时结果: ```text 容器名称 | 镜像用户 | 容器用户 | UID | GID | 状态 -------------------------------------------------------------------------------- RustDeskGen | user | 1000:1000 | 1000 | 1000 | 运行中 Mysql | root | 1026:100 | 1026 | 100 | 运行中 Redis | root | root | 0 | 0 | 运行中 OpenList | openlist | 1026:100 | 1026 | 100 | 已停止 ``` | 字段 | 含义 | | --- | --- | | 容器名称 | Docker 容器名 | | 镜像用户 | 镜像声明的默认启动用户,例如 `user`、`redis`、`root` | | 容器用户 | 创建容器时采用的用户,可能由 Compose 的 `user:` 或运行参数 `--user` 覆盖 | | UID / GID | 运行中的容器:`docker exec id` 的数字身份;已停止的容器:仅解析可确定的数字用户配置 | | 状态 | `运行中` 或 `已停止` | **注意:** 如果停止容器配置的是 `user` 等*用户名*,脚本可能显示 `-`,因为它不启动镜像去解析 `/etc/passwd`;这是有意保留的不确定性,而不是查询失败。 ## 三、为什么镜像用户和容器用户会不同? 例如镜像默认用户为 `user`,但你在 Compose 中加入: ```yaml services: app: image: example/app:latest user: "1026:100" ``` 那么容器可能按 `1026:100` 运行,而非镜像默认身份。**强行统一用户并不一定安全或兼容**:有的应用依赖固定的 HOME 目录、Python 用户级安装路径、初始化权限等,修改后可能无法启动。 更推荐的做法是:**保持镜像推荐的运行用户,再针对它专用的宿主机数据目录授予适当读写权限**。 ## 四、排查目录权限时,再搭配两条命令 **1. 查看某个容器实际运行的进程用户:** ```bash docker top RustDeskGen ``` 镜像可能以 root 启动,然后把业务进程切换到其他用户;所以本篇表格中的 UID/GID **不一定等于所有业务进程的身份**。如果要调整数据库等敏感目录的权限,一定要核对实际应用进程。 **2. 查看容器挂载了哪些群晖文件夹:** ```bash docker inspect RustDeskGen --format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}' ``` 结合容器实际身份、挂载目录及群晖 ACL,判断哪个目录需要授权。不要仅凭表格就批量修改所有文件夹的所有者。 ## 五、常见问题 **Q:为什么显示 `root`,但应用目录仍然报权限错误?** A:`root` 可能只是容器的启动身份。镜像入口脚本可能再切换到 `mysql`、`redis` 等用户;宿主机还可能设置了 ACL 或只读挂载。 **Q:为什么停止的容器 UID/GID 是 `-`?** A:脚本不启动已停止容器,也不猜测镜像内用户名对应的数字 UID/GID;只有明确的数字配置才会直接展示。 **Q:为什么不直接给整个 `/volume1/docker` 设置 Everyone 读写?** A:该目录可能包含数据库、密钥和配置文件。为了排除权限错误而全面开放读写,会扩大安全风险。建议仅授权应用真正需要写入的目录。 **Q:表格出现轻微错位怎么办?** A:请用等宽字体查看 SSH 输出,并适当加宽终端。中文字符在不同终端的显示宽度可能不同;命令用 `|` 分隔列,确保即使视觉上略有偏差也能读清数据。 --- **总结:** 查询容器用户时,先分清「镜像默认用户」「容器配置用户」和「业务进程用户」三个概念,再针对数据挂载目录设置权限,比强制所有 Docker 容器使用同一个用户更稳妥。 Last modification:November 11, 2026 © Allow specification reprint Support Appreciate the author AliPayWeChat Like 1 喜欢我的文章吗? 别忘了点赞或赞赏,让我知道创作的路上有你陪伴。