向容器添加非 root 用户
许多 Docker 镜像使用 root 作为默认用户,但在某些情况下,你可能更倾向于使用非 root 用户。如果你这样做,关于本地文件系统(绑定)挂载有一些你应该了解的特性。具体而言:
-
Docker Desktop for Mac:在容器内部,任何挂载的文件/文件夹的行为就像由你指定的容器用户拥有一样。在本地,所有文件系统操作将改用本地用户的权限。
-
Docker Desktop for Windows:在容器内部,任何挂载的文件/文件夹看起来就像由
root拥有,但你指定的用户仍然能够读/写它们,且所有文件都是可执行的。在本地,所有文件系统操作将改用本地用户的权限。这是因为从根本上没有办法直接将 Windows 样式的文件权限映射到 Linux。 -
Linux 上的 Docker CE/EE:在容器内部,任何挂载的文件/文件夹将具有与容器外部完全相同的权限——包括所有者用户 ID (UID) 和组 ID (GID)。因此,你的容器用户要么需要具有相同的 UID,要么位于具有相同 GID 的组中。用户/组的实际名称并不重要。机器上的第一个用户通常获得 1000 的 UID,因此大多数容器使用此 ID 作为用户 ID 以尝试避免此问题。
为 VS Code 指定用户
如果你正在使用的镜像或 Dockerfile 已经提供了一个可选的非 root 用户(如 node 镜像)但仍默认为 root,你可以通过在 devcontainer.json 中指定 remoteUser 属性,选择让 Visual Studio Code(服务器)及任何子进程(终端、任务、调试)使用它。
"remoteUser": "user-name-goes-here"
在 Linux 上,如果你在 devcontainer.json 中引用了 Dockerfile、镜像或 Docker Compose,这还将自动更新容器用户的 UID/GID 以匹配你的本地用户,从而避免在该环境中存在的绑定挂载权限问题(除非你设置了 "updateRemoteUserUID": false)。
由于此设置仅影响 VS Code 及其相关的子进程,因此需要重启 VS Code(或重新加载窗口)才能生效。但是,UID/GID 更新仅在创建容器时应用,且需要重新构建才能更改。
指定默认容器用户
在某些情况下,你可能需要容器中的所有进程(例如,由于启动要求)而不仅仅是 VS Code 以不同的用户身份运行。执行此操作的方式略有不同,具体取决于你是否正在使用 Docker Compose。
-
Dockerfile 和镜像:将
containerUser属性添加到同一个文件中。"containerUser": "user-name-goes-here"在 Linux 上,与
remoteUser类似,这也会自动更新容器用户的 UID/GID 以匹配你的本地用户,从而避免在该环境中存在的绑定挂载权限问题(除非你设置了"updateRemoteUserUID": false)。 -
Docker Compose:使用以下内容为相应的服务更新(或扩展)你的
docker-compose.yml。user: user-name-or-UID-goes-here
创建非 root 用户
虽然来自 Dev Containers 扩展的任何镜像或 Dockerfile 都会包含一个 UID/GID 为 1000 的非 root 用户(通常称为 vscode 或 node),但许多基础镜像和 Dockerfile 并不包含。幸运的是,你可以更新或创建一个 Dockerfile,向容器中添加一个非 root 用户。
即使在生产环境中,也建议以非 root 用户身份运行应用程序(因为这样更安全),因此即使你正在重复使用现有的 Dockerfile,这也是一个好主意。例如,下面这个用于 Debian/Ubuntu 容器的代码片段将创建一个名为 user-name-goes-here 的用户,赋予其使用 sudo 的能力,并将其设置为默认用户。
ARG USERNAME=user-name-goes-here
ARG USER_UID=1000
ARG USER_GID=$USER_UID
# Create the user
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
#
# [Optional] Add sudo support. Omit if you don't need to install software after connecting.
&& apt-get update \
&& apt-get install -y sudo \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
# ********************************************************
# * Anything else you want to do like clean up goes here *
# ********************************************************
# [Optional] Set the default user. Omit if you want to keep the default as root.
USER $USERNAME
提示: 如果你在构建时遇到关于 GID 或 UID 已存在的错误,说明你选择的镜像可能已经有一个你可以直接利用的非 root 用户。
在任一情况下,如果你已经构建了容器并连接到它,请从命令面板 (F1) 运行 Dev Containers: Rebuild Container(开发容器:重新构建容器)以获取更改。否则,请运行 Dev Containers: Open Folder in Container...(开发容器:在容器中打开文件夹...)以连接到容器。
更改现有容器用户的 UID/GID
虽然在使用 Dockerfile 或镜像时,remoteUser 属性会尝试在 Linux 上自动更新 UID/GID,但你也可以在 Dockerfile 中使用此代码片段来手动更改用户的 UID/GID。请根据需要更新 ARG 值。
ARG USERNAME=user-name-goes-here
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupmod --gid $USER_GID $USERNAME \
&& usermod --uid $USER_UID --gid $USER_GID $USERNAME \
&& chown -R $USER_UID:$USER_GID /home/$USERNAME
请注意,在 Alpine Linux 上,你需要先安装 shadow 软件包。
RUN apk add --no-cache shadow