skip to Main Content

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.

2

Answers


  1. 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
    
    Login or Signup to reply.
  2. 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
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search