Search code examples
dockerubuntuvisual-studio-codedocker-volumevscode-remote

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


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.


Solution

  • 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