How to set user and group in Docker Compose

docker user root masking their identity maybe?

I have this problem: a client’s DevOps is a Mac user, meaning that their Docker works differently on my Linux machine¹. In this case we talk about user and group.

When I create a file using Docker (or, in my case, Docker Compose, the logic is the same in general), the file is created with root privileges, so I cannot directly edit them unless I chown them.

To avoid this we have to set user and group IDs somewhere. There are multiple solutions to this problem: I’ll name a few.

This is our initial docker-compose.yml:

version: '3'
services:
    app:
        image: alpine
        user: "${UID}:${GID}"

I’m told this works out of the box on Mac (I haven’t personally checked), while on Linux, if I run a command, I get a couple of interesting warnings, meaning that the command will be run as root.

For example, let’s print the user’s info. If we open a terminal and run a command we can check we are doing stuff as root (I am going to omit the --rm flag for clarity, but, per the previous post, you may not want to keep the container around when playing with Docker):

$ docker-compose run app id
WARNING: The UID variable is not set. Defaulting to a blank string. 
WARNING: The GID variable is not set. Defaulting to a blank string. 
Creating docker-user-demo_app_run ... done 
uid=0(root) gid=0(root) groups=0(root)
 # other stuff

How can we avoid those warning, and actually set the ownership to my user (which is 1001:1001 on my current machine) while using Docker?

Here are six ways to do it (remember to open a new terminal after each one).

Solution 1: Add variables to the command

This is quick but not ideal:

$ env UID=${UID} GID=${GID} docker-compose run app id
 Creating docker-user-demo_app_run … done
 uid=1001 gid=0(root)

As you can see, user ID is set to my local user_id (1001), while group ID remained the same, because bash sets $GID to empty string (i.e., it’s not set at all).

Solution 2: Variable export

$ export UID=$(id -u)
bash: UID: readonly variable # ignore this, per above
$ export GID=$(id -g)
$ docker-compose run app id
Creating docker-user-demo_app_run … done
 uid=1001 gid=1001

This will work as long as we stay in the current shell. There is margin for some kind of improvement…

Solution 3: Store the variables in the config

To your ~/.bashrc file (works in other shells, of course) append these two lines:

export UID=$(id -u) 
export GID=$(id -g)

Then refresh the file (or open a new terminal):

$ source ~/.bashrc # or '. ~/.bashrc'
bash: UID: readonly variable

and run the usual command:

$ docker-compose run app id
Creating docker-user-demo_app_run ... done 
uid=1001 gid=1001

It works, but that “readonly variable” will haunt you for the rest of your life. I’d rollback the .bashrc file and refresh the terminal again, and move on to the next…

Solution 4: put the user IDs in the command itself

This is the most straightforward, I guess…

$ docker-compose run -u 1001:1001 app id
 Creating docker-user-demo_app_run … done
 uid=1001 gid=1001

This way you can remove the user: line from the docker-compose.yml file.

The problem is that less Docker-savvy coworkers will need to remember to do this. All. The. Time.

Solution 5: add the variables to the .env file

Let’s create an .env file:

UID=1001
GID=1001

And again:

$ docker-compose run app id
 Creating docker-user-demo_app_run … done
 uid=1001 gid=1001

Of course we are not versioning the .env file, so we are going to add a .env.example file for our colleagues and add instructions in the README file.

This is ok, but we are melting Docker logic with app logic. One of the client’s app already has a huge .env file, and we may not want to bloat it with additional stuff in cases like this.

Solution 6: use an override

(Of course this is the solution the DevOps actually implemented after noticing Linux developers screaming all over the (virtual) office).

First we remove the user: line from docker-compose.yml, so it matches the following:

version: '3'
services:
    app:
        image: alpine

Let’s create a docker-compose.override.yml:

version: '3'  
 
services: 
  app: 
    user: 1001:1001

And one last time:

$ docker-compose run app id
 Creating docker-user-demo_app_run … done
 uid=1001 gid=1001

We can add an example file (like docker-compose.override.yml.example ) to version control and instruct the user to copy-paste it to the correct path (which will be added to .gitignore ). They will only need to fill the user info once, and the Docker env will stay separated from the app one.


¹ See this for example