Something that has been baffling me for a while was how to mount a volume in a docker container such that a specific user inside said container would have the same group ID (gid) as a given user outside the container. But, before we delve into that, let's simplify the problem a bit with a few assumptions:
Assumptions
- The "user outside the container" is a user in the docker server. This user can run docker (is in the docker group). We shall call this user outbob, with uid = gid = 1500
- The "user inside the container" is a user in the docker container, which is created by the Dockerfile. This user will be known as inbob, with uid = gid = 1995
- This is a bare example, the smallest proof of concept I could come up with. I may update this article later with a link to a practical application as soon as I shove it in my github account.
The Problem
Let's create a really simple Dockerfile that shows the problem in /tmp/bob for no reason whatsoever.
outbob@dockerbox:/tmp/bob$ cat Dockerfile # Set the base image to Ubuntu FROM ubuntu ENV DEVUSER inbob ENV DEVID 1995 # Create user RUN useradd -m --shell /bin/bash -u $DEVID $DEVUSER USER $DEVUSER ENV WD /home/${DEVUSER} WORKDIR ${WD} outbob@dockerbox:/tmp/bob$
We build the image from that Dockerfile as usual:
outbob@dockerbox:/tmp/bob$ docker build -t bob . Sending build context to Docker daemon 2.048kB Step 1/7 : FROM ubuntu ---> 27941809078c Step 2/7 : ENV DEVUSER inbob ---> Running in e8501dbe8398 [...] ---> Running in 963671a09a5f Removing intermediate container 963671a09a5f ---> a6049000bfda Successfully built a6049000bfda Successfully tagged bob:latest outbob@dockerbox:/tmp/bob$
And run it, passing the same directory we used because I am not in the mood of being original. Yes, I could pass the command I wanted to execute directly from the docker run statement instead of starting bash and then running the command. Deal with it.
outbob@dockerbox:/tmp/bob$ docker run -i --rm -v /tmp/bob:/bob -t bob bash inbob@3d7c097a38d1:~$ id uid=1995(inbob) gid=1995(inbob) groups=1995(inbob) inbob@3d7c097a38d1:~$ exit exit outbob@dockerbox:/tmp/bob$
So far so good. Now what happens if I try to become an user with the same uid and gid as outbob?
outbob@dockerbox:/tmp/bob$ docker run -i --rm -u $(id -u):$(id -g) -v /tmp/bob:/bob -t bob bash groups: cannot find name for group ID 1500 I have no name!@c4355f38747a:/home/inbob$ id uid=1500 gid=1500 groups=1500 I have no name!@c4355f38747a:/home/inbob$ cd /bob I have no name!@c4355f38747a:/bob$ touch nose I have no name!@c4355f38747a:/bob$ ls -lh total 4.0K -rw-r--r-- 1 1500 1500 200 Feb 14 01:01 Dockerfile -rw-r--r-- 1 1500 1500 0 Feb 14 18:31 nose I have no name!@c4355f38747a:/bob$ id inbob uid=1995(inbob) gid=1995(inbob) groups=1995(inbob) I have no name!@c4355f38747a:/bob$
It works in that I can write to the volume but I am now a completely different user; a user with no name. Since I am not Clint Eastwood, I would rather be inbob. Is there a solution?
The Solution (so far)
Note: if you do not want to cut-n-paste the following excerpts, I also put this code in a repo.
We stablished I do not want to find out inbob created something in /tmb/bob as its default gid. The cleanest solution I found so far is to add inbob to the same group outbob used to create /tmb/bob, and then ensure anyone belonging to that group can write to this directory as a member of that group. So, let's do whatever I just said!
Set the directory in question to inherit the gid
We will set the setgid attribute for the directory:
outbob@dockerbox:/tmp/bob$ ls -ld /tmp/bob drwxr----- 2 outbob outbob 4096 Mar 9 08:55 /tmp/bob/ outbob@dockerbox:/tmp/bob$ outbob@dockerbox:/tmp/bob$ chmod 2770 /tmp/bob outbob@dockerbox:/tmp/bob$ outbob@dockerbox:/tmp/bob$ ls -ld /tmp/bob drwxrws--- 2 outbob outbob 4096 Mar 9 08:55 /tmp/bob/ outbob@dockerbox:/tmp/bob$
The little s after the group permissions indicate we have been successful.
Configure the docker build to add user to proper group at runtime
We need to change the Dockerfile a bit and then create a docker-entrypoint.sh file:
outbob@dockerbox:/tmp/bob$ cat Dockerfile # Set the base image to Ubuntu FROM ubuntu ENV DEVUSER inbob ENV DEVUID 1995 ENV DEVGID 1995 ENV EXTGID 1999 # Create user RUN useradd -m --shell /bin/bash -u $DEVUID $DEVUSER # USER $DEVUSER ENV WD /home/${DEVUSER} WORKDIR ${WD} # Put the entrypoint script somewhere we can find COPY docker-entrypoint.sh /entrypoint.sh RUN chmod 0700 /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"] outbob@dockerbox:/tmp/bob$ cat docker-entrypoint.sh #!/bin/sh set -e groupadd -g $EXTGID extgroup adduser $DEVUSER extgroup su - $DEVUSER # And we are done here exec "$@" outbob@dockerbox:/tmp/bob$
Test time!
After we buld the new image we run it passing the default gid for outbob. Nothing stopping us from passing a different gid if it is what we need; docker does not care.
outbob@dockerbox:/tmp/bob$ docker run -i --rm -e EXTGID=$(id -g) -v /tmp/bob:/bob -t bob bash Adding user `inbob' to group `extgroup' ... Adding user inbob to group extgroup Done. inbob@2a7339336f3e:~$ cd /bob inbob@2a7339336f3e:/bob$ ls -l total 8 -rw-r--r-- 1 5000 extgroup 389 Mar 9 13:42 Dockerfile -rw-r--r-- 1 5000 extgroup 122 Mar 9 13:48 docker-entrypoint.sh inbob@2a7339336f3e:/bob$ touch nose inbob@2a7339336f3e:/bob$ ls -l total 8 -rw-r--r-- 1 5000 extgroup 389 Mar 9 13:42 Dockerfile -rw-r--r-- 1 5000 extgroup 122 Mar 9 13:48 docker-entrypoint.sh -rw-rw-r-- 1 inbob extgroup 0 Mar 9 14:00 nose inbob@2a7339336f3e:/bob$ exit logout root@2a7339336f3e:/home/inbob# exit exit outbob@dockerbox:/tmp/bob$ ls -l total 8 -rw-r--r-- 1 outbob outbob 122 Mar 9 08:48 docker-entrypoint.sh -rw-r--r-- 1 outbob outbob 389 Mar 9 08:42 Dockerfile -rw-rw-r-- 1 1995 outbob 0 Mar 9 09:00 nose outbob@dockerbox:/tmp/bob$ rm nose outbob@dockerbox:/tmp/bob$
Other than getting out is now a two-step process, which may not be important when running a container in the background, the only telltale we were up to not good is that the uid for the file we created does not match any in our docker server. But, outbob can still delete the file.
And that is how we bring harmony in the divided worlds of bob.