允许 Docker 容器和主机用户写入绑定挂载主机目录

Allow Docker Container & Host User To Write on Bind Mounted Host Directory

感谢来自任何来源的任何帮助。
服务器有一个 Docker 容器,带有 alpine、nginx、php。只有当我将“chown -R nobody directory”设置为主机目录时(没有人是容器中的用户),此容器才能写入绑定挂载的主机目录。 我正在使用 VSCode 的扩展“Remote - SSH”作为用户 ubuntu 连接到服务器。 VSCode 能够编辑同一主机目录中的文件(用于绑定挂载),只有当我设置“chown -R ubuntu 目录”时。

问题:如果我将“ubuntu”设置为所有者,容器将无法写入(使用php写入),如果我将“nobody”设置为所有者,VSCode SSH不能写。我正在寻找一种方法,让两者都可以在不一次又一次地更改目录所有者用户的情况下进行写入,或者类似的轻松。

使用的图片:https://hub.docker.com/r/trafex/php-nginx

我尝试了什么:
在 Container 中,我将用户“nobody”添加到组“ubuntu”。在主机上,目录(用作挂载)设置为“sudo chown -R ubuntu:ubuntu 目录”,用户“ubuntu”已添加到组“ubuntu”。
VSCode 已编辑,容器无法编辑。 (编辑:成功了,我更改了组的目录权限以允许写入)

编辑:已经创建的容器没有 Dockerfile 也 运行 并且可能进行了重要更改编辑,所以我可能无法使用 Dockerfile 或 entrypoint.sh解决问题的方法。是否可以通过容器内的 运行 命令或无需再次创建容器来实现?此容器可以停止。

编辑:我想知道,在 Triet Doan 的回答中,一个选项是修改容器中已创建用户的 UID 和 GID,对用户和组“nobody”执行此操作会导致容器内部出现任何问题,我想知道,因为可能许多设置命令已经在容器内执行,文件已经由 php 在挂载目录上编辑,并且容器 运行 天

编辑:我发现 alpine 没有 usermod 和 groupmod。

This article 把这个问题写得很好。我只是在这里总结一下主要思想。

解决此权限问题的最简单方法是将容器中的 UID 和 GID 修改为主机中使用的相同 UID 和 GID。

在您的情况下,我们尝试获取用户 ubuntu 的 UID 和 GID 并在容器中使用它们。


作者推荐3种方式:

1。在 entrypoint.sh.

中创建一个与主机相同的 UID 和 GID 的新用户

这是 Ubuntu 基础镜像的 Dockerfile 版本。

FROM ubuntu:latest

RUN apt-get update && apt-get -y install gosu
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

entrypoint.sh创建如下:

#!/bin/bash

USER_ID=${LOCAL_UID:-9001}
GROUP_ID=${LOCAL_GID:-9001}

echo "Starting with UID: $USER_ID, GID: $GROUP_ID"
useradd -u $USER_ID -o -m user
groupmod -g $GROUP_ID user
export HOME=/home/user

exec /usr/sbin/gosu user "$@"

只需使用 docker build 命令构建容器。

docker build -t ubuntu-test1 .

LOCAL_UIDLOCAL_GID可以在docker run命令中传递给容器

$ docker run -it --name ubuntu-test -e LOCAL_UID=$(id -u $USER) -e LOCAL_GID=$(id -g $USER) ubuntu-test1 /bin/bash
Starting with UID: 1001, GID: 1001

user@1291224a8029:/$ id
uid=1001(user) gid=1001(user) groups=1001(user)

我们可以看到容器中的UID和GID与宿主机中的相同

2。将主机的 /etc/passwd/etc/group 挂载到容器

这也是一个很好的方法,一目了然。这种方法的一个缺点是在容器中创建的新用户无法访问 bind-mounted 文件和目录,因为 UID 和 GID 与主机的不同。

必须注意 /etc/passwd/etc/group 具有 read-only 访问权限,否则容器可能会访问并覆盖主机的 /etc/passwd/etc/group.所以笔者不推荐这种方式。

$ docker run -it --name ubuntu-test --mount type=bind,source=/etc/passwd,target=/etc/passwd,readonly --mount type=bind,source=/etc/group,target=/etc/g
roup,readonly -u $(id -u $USER):$(id -g $USER) ubuntu /bin/bash

ether@903ad03490f3:/$ id
uid=1001(user) gid=1001(user) groups=1001(user)

3。修改UID和GID为宿主机相同的UID和GID

这与第一种方法基本相同,但只需修改 UID 和 GID,以防已在容器中创建新用户。假设您在 Dockerfile 中创建了一个新用户,然后只需在 Dockerfile 或 entrypoint.sh.

中调用这些命令

如果你的用户名和组名是“test”,那么你可以使用usermodgroupmod命令修改容器中的UID和GID。从主机获取的 UID 和 GID 作为环境变量将用于此“​​测试”用户。

usermod -u $USER_ID -o -m -d <path-to-new-home> test
groupmod -g $GROUP_ID test

Problem: if I set "ubuntu" as owner, container can't write (using php to write), if I set "nobody" as owner, VSCode SSH can't write. I am finding a way to allow both to write without changing directory owner user again and again, or similar ease.

首先,我建议容器映像应该为容器内的文件创建一个新用户名,而不是重复使用 nobody,因为该用户也可能用于其他 OS 任务不应该有任何特殊访问权限。

接下来,正如 Triet 所建议的,调整容器 user/group 以匹配容量的入口点是首选。我自己的这些脚本版本可以在 this base image that includes a fix-perms 脚本中找到,该脚本使容器用户的用户 ID 和组 ID 与已安装卷的 ID 相匹配。特别是该脚本的以下行,其中 $opt_u 是容器用户名,$opt_g 是容器组名称,</code> 是卷安装位置:</p> <pre><code># update the uid if [ -n "$opt_u" ]; then OLD_UID=$(getent passwd "${opt_u}" | cut -f3 -d:) NEW_UID=$(stat -c "%u" "") if [ "$OLD_UID" != "$NEW_UID" ]; then echo "Changing UID of $opt_u from $OLD_UID to $NEW_UID" usermod -u "$NEW_UID" -o "$opt_u" if [ -n "$opt_r" ]; then find / -xdev -user "$OLD_UID" -exec chown -h "$opt_u" {} \; fi fi fi # update the gid if [ -n "$opt_g" ]; then OLD_GID=$(getent group "${opt_g}" | cut -f3 -d:) NEW_GID=$(stat -c "%g" "") if [ "$OLD_GID" != "$NEW_GID" ]; then echo "Changing GID of $opt_g from $OLD_GID to $NEW_GID" groupmod -g "$NEW_GID" -o "$opt_g" if [ -n "$opt_r" ]; then find / -xdev -group "$OLD_GID" -exec chgrp -h "$opt_g" {} \; fi fi fi

然后我以 root 身份启动容器,容器从入口点运行 fix-perms 脚本,然后是 command similar to:

exec gosu ${container_user} ${orig_command}

这会将作为 root 的 运行 的入口点替换为作为指定用户的应用程序 运行。我有更多这样的例子:

What I tried: In Container, I added user "nobody" to group "ubuntu". On host, directory (used as mount) was set "sudo chown -R ubuntu:ubuntu directory", user "ubuntu" was already added to group "ubuntu". VSCode did edit, container was unable to edit.

我会避免这种情况并创建一个新用户。没有人被设计成尽可能没有特权,所以给它更多的访问权限可能会产生意想不到的后果。

Edit: the container already created without Dockerfile also ran and maybe edited with important changes, so maybe I can't use Dockerfile or entrypoint.sh way to solve problem. Can It be achieved through running commands inside container or without creating container again? This container can be stopped.

这是容器中相当大的代码味道。它们应该被设计成短暂的。如果您不能轻松替换它们,您将失去升级到更新图像的能力,并产生大量您最终需要清理的状态漂移。您应该保留的更改需要放在一个卷中。如果删除容器时还有其他更改会丢失,它们将在 docker diff 中可见,我建议现在修复此问题而不是增加技术债务的规模。

Edit: I am wondering, in Triet Doan's answer, an option is to modify UID and GID of already created user in the container, will doing this for the user and group "nobody" can cause any problems inside container, I am wondering because probably many commands for settings already executed inside container, files are already edited by php on mounted directory & container is running for days

我会构建一个不依赖于此用户名的较新图像。在容器内,如果有需要保存的数据,应该在一个卷中。

Edit: I found that alpine has no usermod & groupmod.

我在 entrypoint script to install it on the fly, but the shadow package should be included in the image you build 中使用以下内容,而不是为每个新容器即时执行此操作:

if ! type usermod >/dev/null 2>&1 || \
   ! type groupmod >/dev/null 2>&1; then
  if type apk /dev/null 2>&1; then
    echo "Warning: installing shadow, this should be included in your image"
    apk add --no-cache shadow
  else
    echo "Commands usermod and groupmod are required."
    exit 1
  fi
fi