Permission problems in bind mount in Docker Volume

Container Image

Volumes are used for persistent-storage for docker containers. Bind mounts have been around and it refers to the absolute path of the host machine to read and write data while volumes can be generated on Docker storage and volumes are not dependent on the file and the directory structure of the host machine.

Please consider using volumes when you need persistent-storage and you can use tmpfs mount to avoid storing the data anywhere permanently, and to increase the container’s performance by avoiding writing into the container’s writable layer.

This is to talk about permission problems between the host side and the container side when we use bind mounts in Docker.

I went through this article and wanted to add some explanations for what I could not understand at first. Also this article covers new flag --mount that had been introcued since Docker 17.06. The flag --mount is more explicit and verbose than the traditional flag --volume in general.

The problems are significant for bind mounts when the host environment file and directory structure affect container’s environment. For example, if we create a volume and mount into /tmp in a container, Docker software manages this volume and it’s run as a root in both host and container sides.

In this case both local volume and the mounted point in the container are modified by root user.

$ docker volume create my-vol
$ docker volume ls
DRIVER              VOLUME NAME
local               my-vol
$ docker run -it --name test --mount source=my-vol,target=/tmp busybox
$ docker inspect test

"Mounts": [
    {
        "Type": "volume",
        "Name": "my-vol",
        "Source": "/var/lib/docker/volumes/my-vol/_data",
        "Destination": "/tmp",
        "Driver": "local",
        "Mode": "z",
        "RW": true,
        "Propagation": ""
    }
]

What were the problems in bind mounts though?? Tha author mentioned 2 problems in the article but let’s describe it in 1 word. It is that … local user and group in a container do not match local user and group in host machine so there are some problems due to this such as …

1. If you write to the volume you won’t be able to access the files that container has written because the process in the container usually runs as root.

2. You shouldn’t run the process inside your containers as root but even if you run as some hard-coded user it still won’t match the user on your laptop/jenkins/staging.

HANDLING PERMISSIONS WITH DOCKER VOLUMES

Let’s confirm how the problem happens next. Assume we have the local UID and GID 1001 for a user named ‘user’ as below.

$ id
uid=1001(user) gid=1001(user) groups=1001(user),999(docker)

Next create a tmp directory and a sample file in the host machine as below and mount it into a container’s tmp directory.

You can confirm the mounted directory and file in the container appeared with the same host’s UID/GID 1001 (user) in the container. This is the expected behavior.

But these UID and GID do not exist in this container originally. First point is sure of that the container must recognize these UID and GID, plus it needs to handle this file and directory with the same UID/GID in the host machine, not root user of the container.

$ mkdir -p tmp && touch tmp/sample
$ docker run -it --name test --mount type=bind,source="$(pwd)"/tmp,target=/tmp busybox

/ # ls -al tmp/
total 12
drwxrwxr-x    2 1001     1001          4096 Oct  2 11:47 .
drwxr-xr-x    1 root     root          4096 Oct  2 12:38 ..
-rw-rw-r--    1 1001     1001             7 Oct  2 11:47 sample

What is the second problem?? Now let’s create a test file in this directory of the container. The file is created by root user in the container.

/ # touch tmp/test
/ # ls -al tmp/
total 12
drwxrwxr-x    2 1001     1001          4096 Oct  2 12:45 .
drwxr-xr-x    1 root     root          4096 Oct  2 12:44 ..
-rw-rw-r--    1 1001     1001             7 Oct  2 11:47 sample
-rw-r--r--    1 root     root             0 Oct  2 12:45 test

If you think this mounted directory are usable from the host machine it won’t work as you expected. Why? This test file is seen as root user’s file on the host mahine too. Here’s the result of ls command on the host machine. The “test” file appears as root user’s file in user’s directory of the host machine.

$ ls -al tmp/
total 12
drwxrwxr-x 2 user user 4096 Oct  2 21:45 .
drwxrwxr-x 3 user user 4096 Oct  2 20:47 ..
-rw-rw-r-- 1 user user    7 Oct  2 20:47 sample
-rw-r--r-- 1 root root   0  Oct  2 21:45 test

What we need to do is that the same UID and GID passed to the container to handle the mounted file and directories, also the user who has the same UID and GID of the host machine access the mounted files in the container.

There are 2 intoroduced measures that do not work well.

  1. Hard-code a UID in Dockerfile and add a user in a container
  2. User -u option to provide UID to a container when you run it

The author suggested to use entrypoint.sh and pass UID/GID from the host machine then create a user with the same UID/GID in a container. This works well but there are other alternatives too. I’ll cover 3 ways to cope with this problem in the following section.

  1. entrypoint.sh creates a new user with the same UID and GID of the host machine
  2. Mount the host machine’s /etc/passwd and /etc/group to a container
  3. Modify UID and GID with the same UID and GID of the host machine (this is a case if a container has created a user

entrypoint.sh creates a new user with the same UID and GID of the host machine

Here’s the Dockerfile version for Ubuntu base image. The entrypoint.sh was created as follows also.

docker build -t ubuntu-test1 .

Local UID and GID can be passed to the container and in the container the same UID and GID can be used.

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

Sweet. Next let’s mount the host’s tmp directory to the container’s /tmp and check that the container’s user switches to the same UID/GID user of the host machine. Also the created file by that container’s user will be manageable on the host machine’s user, not by root user of the host.

$ docker run -it --name ubuntu-test --mount type=bind,
source="$(pwd)"/tmp,target=/tmp -e LOCAL_UID=$(id -u $USER) -e LOCAL_GID=$(id -g $USER) ubuntu-t
est1 /bin/bash
Starting with UID: 1001, GID: 1001
user@1aef3a8ec72b:/$ ls -al /tmp/
total 12
drwxrwxr-x 2 user user 4096 Oct  2 13:32 .
drwxr-xr-x 1 root root 4096 Oct  2 13:47 ..
-rw-rw-r-- 1 user user    7 Oct  2 11:47 sample

I created a file named “test” in tmp directory in the container. The file “test” now appears in the host machine’s tmp directory and accessible by the local user with the same user’s UID and GID as below.

user@1aef3a8ec72b:/$ touch /tmp/test
user@1aef3a8ec72b:/$ exit
exit

$ ls -al tmp/
total 12
drwxrwxr-x 2 user user 4096 Oct  2 22:50 .
drwxrwxr-x 3 user user 4096 Oct  2 22:41 ..
-rw-rw-r-- 1 user user    7 Oct  2 20:47 sample
-rw-r--r-- 1 user user    0 Oct  2 22:50 test

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

This is also a fine approach and more 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.

You must be careful to have /etc/passwd and /etc/group with readonly access otherwise a container might access and overwrite your host machine’s /etc/passwd and /etc/group, so I don’t recommend doing 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)

Modify UID and GID with the same UID and GID of the host machine

This is mostly the same approach of 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 is set in Dockerfile then just call these commands in either Dockerfile or entrypoint.sh.

If your user name and groupname 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.

To wrap up, the most easiest way to tackle with this permission problem is just to modify UID and GID in the container to the same UID and GID that are used in the host machine. If not, you can create a new user with the same UID and GID of the host machine.

A user name can be different in both cases because the filesystem doesn’t care what user name was taken “user” or “test”, but it cares about a numeric ID number attached to that user. As long as a user id is kept, it will work fine in both the host and container sides.

Leave a Reply

Your email address will not be published. Required fields are marked *