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

2

5

Any help from any source is appreciated.
Server has a Docker container with alpine, nginx, php. This container is able to write in bind mounted host directory, only when I set "chown -R nobody directory" to the host directory (nobody is a user in container). I am using VSCode's extension "Remote - SSH" to connect to server as user ubuntu. VSCode is able to edit files in that same host directory (being used for bind mount), only when I set "chown -R ubuntu directory".

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.

Image used: https://hub.docker.com/r/trafex/php-nginx

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: IT WORKED, I changed the directory permission for the group to allow write)

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.

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.

Janie answered 19/4, 2022 at 1:30 Comment(2)
please add your Dockerfiles to the questionDogcart
@Dogcart no Dockerfiles available, ran it with commandJanie
M
3

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.

First, I'd recommend the container image should create a new username for the files inside the container, rather than reusing nobody since that user may also be used for other OS tasks that shouldn't have any special access.

Next, as Triet suggests, an entrypoint that adjusts the container's user/group to match the volume is preferred. My own version of these scripts can be found in this base image that includes a fix-perms script that makes the user id and group id of the container user match the id's of a mounted volume. In particular, the following lines of that script where $opt_u is the container username, $opt_g is the container group name, and $1 is the volume mount location:

# update the uid
if [ -n "$opt_u" ]; then
  OLD_UID=$(getent passwd "${opt_u}" | cut -f3 -d:)
  NEW_UID=$(stat -c "%u" "$1")
  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" "$1")
  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

Then I start the container as root, and the container runs the fix-perms script from the entrypoint, followed by a command similar to:

exec gosu ${container_user} ${orig_command}

This replaces the entrypoint that's running as root with the application running as the specified user. I've got more examples of this in:

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.

I'd avoid this and create a new user. Nobody is designed to be as unprivileged as possible, so there could be unintended consequences with giving it more access.

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.

This is a pretty big code smell in containers. They should be designed to be ephemeral. If you can't easily replace them, you're missing the ability to upgrade to a newer image, and creating a lot of state drift that you'll eventually need to cleanup. Your changes that should be preserved need to be in a volume. If there are other changes that would be lost when the container is deleted, they will be visible in docker diff and I'd recommend fixing this now rather than increasing the size of the technical debt.

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

I would build a newer image that doesn't depend on this username. Within the container, if there's data you need to preserve, it should be in a volume.

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

I use the following in the entrypoint script to install it on the fly, but the shadow package should be included in the image you build rather than doing this on the fly for every new container:

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
Mcabee answered 23/4, 2022 at 13:47 Comment(2)
thanks for the answer. PHP site is now editing files when directory owner is set to nobody. But If I create a new user in running container or in a new container, can PHP site then edit files? or I need to do some linux settingsJanie
@Janie the user can write if the user running the process matches the user that owns the files and permissions are set to allow yourself to write.Mcabee
S
7

This article wrote about this problem very nicely. I would just summarize the main ideas here.

The easiest way to tackle with this permission problem is to modify UID and GID in the container to the same UID and GID that are used in the host machine.

In your case, we try to get the UID and GID of user ubuntu and use them in the container.


The author suggests 3 ways:

1. Create a new user with the same UID and GID of the host machine in entrypoint.sh.

Here’s the Dockerfile version for Ubuntu base image.

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"]

The entrypoint.sh was created as follows:

#!/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 "$@"

Simply build the container with the docker build command.

docker build -t ubuntu-test1 .

The LOCAL_UID and LOCAL_GID can be passed to the container in the docker run command.

$ 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)

We can see that the UID and GID in the container are the same as those in the host.

2. Mount the host machine’s /etc/passwd and /etc/group to a container

This is also a fine approach and simpler at a glance. One drawback of this approach is that a new user created in a container can’t access the bind-mounted file and directories because UID and GID are different from the host machine’s ones.

One must be careful to have /etc/passwd and /etc/group with read-only access, otherwise the container might access and overwrite the host machine’s /etc/passwd and /etc/group. Therefore, the author doesn't recommend this way.

$ 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. Modify UID and GID with the same UID and GID of the host machine

This is mostly the same approach as No.1, but just modify the UID and GID in case a new user has been created in the container already. Assume you have a new user created in the Dockerfile, then just call these commands in either Dockerfile or entrypoint.sh.

If your username and group name were "test", then you can use usermod and groupmod commands to modify UID and GID in the container. The taken UID and GID as environment variables from the host machine will be used for this "test" user.

usermod -u $USER_ID -o -m -d <path-to-new-home> test
groupmod -g $GROUP_ID test
Swordplay answered 21/4, 2022 at 10:19 Comment(5)
thanks for answer, will modifying UID and GID for the user & group "nobody" can cause any problems inside container, probably many commands for settings already executed inside container, files are already edited by php on mounted directory & container is running for daysJanie
That might happen. But if all your data are in the mounted volume, you can just start another new container with that volume and it should continue working fine. So, instead of modifying the running container, I would suggest stopping the current one, then start a complete new one with proper UID-GID and see how it behaves.Swordplay
it says man7.org/linux/man-pages/man8/usermod.8.html -m(--move-home) is only valid in combination with the -d(--home) for usermodJanie
Then, we can add the -d option to the command. I've updated my answer.Swordplay
It is always better to write commands using long named arguments, for example --move-home instead of -m. This lets others to read and understand code snippets on the fly, without interruptions to read man for command options. Same rule applies to config files, such as Dockerfile, gitlab-ci or whatever if you care about your teammates. Short syntax is good for daily use, but less readable.Majuscule
M
3

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.

First, I'd recommend the container image should create a new username for the files inside the container, rather than reusing nobody since that user may also be used for other OS tasks that shouldn't have any special access.

Next, as Triet suggests, an entrypoint that adjusts the container's user/group to match the volume is preferred. My own version of these scripts can be found in this base image that includes a fix-perms script that makes the user id and group id of the container user match the id's of a mounted volume. In particular, the following lines of that script where $opt_u is the container username, $opt_g is the container group name, and $1 is the volume mount location:

# update the uid
if [ -n "$opt_u" ]; then
  OLD_UID=$(getent passwd "${opt_u}" | cut -f3 -d:)
  NEW_UID=$(stat -c "%u" "$1")
  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" "$1")
  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

Then I start the container as root, and the container runs the fix-perms script from the entrypoint, followed by a command similar to:

exec gosu ${container_user} ${orig_command}

This replaces the entrypoint that's running as root with the application running as the specified user. I've got more examples of this in:

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.

I'd avoid this and create a new user. Nobody is designed to be as unprivileged as possible, so there could be unintended consequences with giving it more access.

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.

This is a pretty big code smell in containers. They should be designed to be ephemeral. If you can't easily replace them, you're missing the ability to upgrade to a newer image, and creating a lot of state drift that you'll eventually need to cleanup. Your changes that should be preserved need to be in a volume. If there are other changes that would be lost when the container is deleted, they will be visible in docker diff and I'd recommend fixing this now rather than increasing the size of the technical debt.

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

I would build a newer image that doesn't depend on this username. Within the container, if there's data you need to preserve, it should be in a volume.

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

I use the following in the entrypoint script to install it on the fly, but the shadow package should be included in the image you build rather than doing this on the fly for every new container:

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
Mcabee answered 23/4, 2022 at 13:47 Comment(2)
thanks for the answer. PHP site is now editing files when directory owner is set to nobody. But If I create a new user in running container or in a new container, can PHP site then edit files? or I need to do some linux settingsJanie
@Janie the user can write if the user running the process matches the user that owns the files and permissions are set to allow yourself to write.Mcabee

© 2022 - 2024 — McMap. All rights reserved.