Understanding user file ownership in docker: how to avoid changing permissions of linked volumes

Consider the following trivial Dockerfile:

FROM debian:testing
RUN  adduser --disabled-password --gecos '' docker
RUN  adduser --disabled-password --gecos '' bob 

in a working directory with nothing else. Build the docker image:

docker build -t test .

and then run a bash script on the container, linking the working directory into a new subdir on bob's home directory:

docker run --rm -it -v $(pwd):/home/bob/subdir test 

Who owns the contents of subdir on the container? On the container, run:

cd /home/bob/subdir
ls -l

ad we see:

-rw-rw-r-- 1 docker docker 120 Oct 22 03:47 Dockerfile

Holy smokes! docker owns the contents! Back on the host machine outside the container, we see that our original user still owns the Dockerfile. Let's try and fix the ownership of bob's home directory. On the container, run:

chown -R bob:bob /home/bob
ls -l 

and we see:

-rw-rw-r-- 1 bob bob 120 Oct 22 03:47 Dockerfile

But wait! outside the container, we now run ls -l

-rw-rw-r-- 1 1001 1001 120 Oct 21 20:47 Dockerfile

we no longer own our own file. Terrible news!


If we had only added one user in the above example, everything would have gone more smoothly. For some reason, Docker seems to be making any home directory owned by the first non-root user it encounters (even if that user is declared on an earlier image). Likewise, this first user is the one that corresponds to the same ownership permissions as my home user.

Question 1 Is that correct? Can someone point me to documentation of this, I'm just conjecturing based on the above experiment.

Question 2: Perhaps this is just because they both have the same numerical value on the kernel, and if I tested on a system where my home user was not id 1000 then permissions would get changed in every case?

Question 3: The real question is, of course, 'what do I do about this?' If bob is logged in as bob on the given host machine, he should be able to run the container as bob and not have file permissions altered under his host account. As it stands, he actually needs to run the container as user docker to avoid having his account altered.

I hear you asking Why do I have such a weird Dockerfile anyway?. I wonder too sometimes. I am writing a container for a webapp (RStudio-server) that permits different users to log in, which simply uses the user names and credentials from the linux machine as the valid user names. This brings me the perhaps unusual motivation of wanting to create multiple users. I can get around this by creating the user only at runtime and everthing is fine. However, I use a base image that has added a single docker user so that it can be used interactively without running as root (as per best practice). This ruins everything since that user becomes the first user and ends up owning everything, so attempts to log on as other users fail (the app cannot start because it lacks write permissions). Having the startup script run chown first solves this issue, but at the cost of linked volumes changing permissions (obviously only a problem if we are linking volumes).


Solution 1:

Two options I've found:

CHOWN all the things (after doing your work)

I've done docker run -v `pwd`/shared:/shared image, and the container has created files within pwd/shared that are how owned by the docker process. However, /shared is still owned by me. So within the docker process, I do

chown -R `stat -c "%u:%g" /shared` /shared

stat -c "%u:%g" /shared returns 1000:1000 in my case, being the uid:gid of my user. Even though there is no user 1000 within the docker conatainer, the id is there (and stat /shared just says "unknown" if you ask for the username).

Anyway, chown obediently transfers ownership of the contents of /shared to 1000:1000 (which, as far as it is concerned, doesn't exist, but outside the container, it's me). So I now own all the files. The container can still modify things if it wants to, because from its perspective, it's root.

And all is well with the world.

docker run -u so all files created will automatically have the right owner

Another way to do this is the -u flag on docker run.

docker run -v `pwd`/shared:/shared -u `stat -c "%u:%g" /shared` ubuntu bash

This way, the docker user inside the container is youruid:yourgid.

However: this means giving up your root authority within the container (apt-get install, etc.). Unless you create a user with that new uid and add it to the root group.

Solution 2:

Is that correct? Can someone point me to documentation of this, I'm just conjecturing based on the above experiment.

Perhaps this is just because they both have the same numerical value on the kernel, and if I tested on a system where my home user was not id 1000 then permissions would get changed in every case?

Have a read of info coreutils 'chown invocation', that might give you a better idea of how file permissions / ownership works.

Basically, though, each file on your machine has a set of bits tacked on to it that defines its permissions and ownership. When you chown a file, you're just setting these bits.

When you chown a file to a particular user/group using the username or group name, chown will look in /etc/passwd for the username and /etc/group for the group to attempt to map the name to an ID. If the username / group name doesn't exist in those files, chown will fail.

root@dc3070f25a13:/test# touch test
root@dc3070f25a13:/test# ll
total 8
drwxr-xr-x  2 root root 4096 Oct 22 18:15 ./
drwxr-xr-x 22 root root 4096 Oct 22 18:15 ../
-rw-r--r--  1 root root    0 Oct 22 18:15 test
root@dc3070f25a13:/test# chown test:test test
chown: invalid user: 'test:test'

However, you can chown a file using IDs to whatever you want (within some upper positive integer bounds, of course), whether there is a user / group that exists with those IDs on your machine or not.

root@dc3070f25a13:/test# chown 5000:5000 test
root@dc3070f25a13:/test# ll
total 8
drwxr-xr-x  2 root root 4096 Oct 22 18:15 ./
drwxr-xr-x 22 root root 4096 Oct 22 18:15 ../
-rw-r--r--  1 5000 5000    0 Oct 22 18:15 test

The UID and GID bits are set on the file itself, so when you mount those files inside your docker container, the file has the same owner / group UID as it does on the host, but is now mapped to /etc/passwd in the container, which is probably going to be a different user unless it's owned by root (UID 0).

The real question is, of course, 'what do I do about this?' If bob is logged in as bob on the given host machine, he should be able to run the container as bob and not have file permissions altered under his host account. As it stands, he actually needs to run the container as user docker to avoid having his account altered.

It seems like, with your current set-up, you'll need to make sure your UIDs > usernames in /etc/passwd on your host match up to your UIDs > usernames in your containers /etc/passwd if you want to interact with your mounted user directory as the same user that's logged in on the host.

You can create a user with a specific user id with useradd -u xxxx. Buuuut, that does seem like a messy solution...

You might have to come up with a solution that doesn't mount a host users home directory.

Solution 3:

So, I ended up in this post looking on how to restore ownership of all the files (owned by root) that came out of a docker container running as root, to my non-privileged user in the host.

I needed the process inside the container to run as root, so I can't use -u on docker run.

I'm not proud of what I did, but at the end of my bash script, I added this:

docker run --rm -it \
    --entrypoint /bin/sh \
    -e HOST_UID=`id -u` \
    -v ${HOST_FOLDER_OWNED_BY_ROOT}:/tmp \
    alpine:latest \
    -c 'chown -R ${HOST_UID}:${HOST_UID} /tmp/'

Let's break some of the lines down:

  • Run /bin/sh inside the container:

--entrypoint /bin/sh

  • Pass the current user's uid as an environment variable to the container:

-e HOST_UID=`id -u`

  • Mount whatever folder you want to re-own back to your user (filled with files owned by root, output-ed by the previous container that ran as root), under this new container's /tmp:

-v ${HOST_FOLDER_OWNED_BY_ROOT}:/tmp

  • Run chown recursively with the host user's uid over the target directory (mounted inside the container in /tmp):

-c 'chown -R ${HOST_UID}:${HOST_UID} /tmp/'

So, with this, I got the files owned back to my current user without having to "escalate" privileges to root or to sudo.

It's dirty, but it worked. Hope I helped.