Running Docker Containers as a Non-root User with a Custom UID / GID
If you're not using Docker Desktop and your UID / GID is not 1000 then you'll get permission errors with volumes. Here's how to fix that.
If you prefer video, I recorded a YouTube video going over what’s written below in more detail.
In my DockerCon 2021 talk I covered running containers as a non-root user.
It’s a good idea to do this for security purposes but it’s also really useful in every day development and deploying to Linux servers.
For example if your container was running as root and you generated a file from
your container through a volume back to your Docker host then the file will be
owned by root:root
. That could make it annoying to edit from your dev box
because you would need elevated privileges to write to or delete that file.
If you’re using Docker Desktop it will handle fixing file permissions for you but if you’re using native Linux (or WSL 2 without Docker Desktop) it won’t get fixed automatically.
# Checking your UID and GID
This becomes a problem for running containers as root but also if you happen to
have a user id and group id that’s not 1000:1000
. You can check by running
the id
command in your terminal.
Mine looks like this from within WSL 2:
$ id
uid=1000(nick) gid=1000(nick) groups=1000(nick),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),1001(docker)
The important takeaway here is my UID and GID are 1000
and by default if you
create a user in your Dockerfile and switch to it then it will be 1000
in
your image. This means file permissions will work great.
In a lot of cases on Linux you will have 1000
because that is the default
value for the first user created on your system but in a multi-user or
uncontrolled environment you could end up with having 1001
or something
higher.
# Custom UID / GID in Your Docker Related Files
All of my example Docker web apps (Flask, Rails, Django, Node and Phoenix) are now using the patterns we’re going to cover below if you’re looking for end to end examples.
We’ll want to introduce UID
and GID
build ARG
s and ensure all groups and
users in the Dockerfile
reference it. Then we’ll set them in the
docker-compose.yml
file and populate their values in an .env
file. This way
you only have to change the values in your .env
file and re-build your image.
None of this is specific to Docker Compose too. That’s just how my examples are set up.
Defining UID and GID build ARGs in your Dockerfile
I’m only going to include the relevant bits and pieces of the Dockerfile here. The example apps have fully working Dockerfiles.
ARG UID=1000
ARG GID=1000
RUN groupadd -g "${GID}" python \
&& useradd --create-home --no-log-init -u "${UID}" -g "${GID}" python
USER python
# Everything after this point will run as the python user with a specific UID / GID
The basic idea is:
- Define (2) new build args for the ids (defaults to 1000 if you don’t customize it)
- Create a new
python
group with that specific group id - Create a new
python
user that uses both the user id and group id ARGs - Switch to the
python
user
Step 2 is necessary to do as an explicit step. Configuring useradd
to create
a group with the name as the user didn’t work – at least not for me.
Also, if you’re dealing with something like the Node image where it creates a
default node
user for you by default you can do this instead:
RUN groupmod -g "${GID}" node && usermod -u "${UID}" -g "${GID}" node
The only difference here is we’re modifying the existing group and user instead of creating a new one.
Setting the Build ARGs in Your docker-compose.yml File
The above will default to 1000
but now we need a way to use different values
if we choose to do so.
services:
web:
build:
context: "."
target: "app"
args:
- "UID=${UID:-1000}"
- "GID=${GID:-1000}"
I covered variable interpolation in my DockerCon 21 talk but this will read UID
and GID
environment variables from your
.env
file on your Docker host at build time. If your .env
file doesn’t
define them they will default to 1000
.
I like this pattern because it means you only ever need to change these values
in your .env
file and run a docker compose build
. Alternatively you can
avoid using variable interpolation and set explicit flags at build time with
docker compose build --build-arg UID=1002 --build-arg GID=1002
, this could
could be useful to read these values dynamically on your Docker host.
Customizing the values in your .env file
By default you can leave them undefined to use 1000
or set them in your
.env
file like this to use whichever values you want:
export UID=1002
export GID=1002
Now you can docker compose build
your image with a custom UID and GID.
One important takeaway is if you plan to customize and change these values
you’ll need to re-build your image after changing them. An image built with
1001
will be baked into the image as 1001
. A user with 1000
on a
different Docker host will have permission issues.
In practice this isn’t a problem. In development you can define these ENV vars
before building your image and you’re good to go. In production, if you really
need to use something other than 1000
you can build your images with whatever
ids you need in your CI environment or where ever you plan to build + push your
image so they match prod.
# Demo Video
Timestamps
- 0:07 – Why is this important when using Docker volumes?
- 1:19 – Starting the project to see what we’re working with
- 2:13 – What if your uid/gid isn’t 1000? Demo’ing the problem
- 5:00 – Setting build args for UID / GID and assigning them to a group and user
- 9:14 – Passing in the build args from the docker-compose.yml file
- 10:33 – Checking out the new env vars in the .env file
- 12:29 – Changing the UID and GID env vars for our other user to fix the issue
- 14:55 – Updating a file to make sure volumes are working
- 15:26 – How would you have solved this problem?
How do you handle custom UIDs and GIDs in your images? Let us know below.