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