Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Running Docker Containers as a Non-root User with a Custom UID / GID

blog/cards/running-docker-containers-as-a-non-root-user-with-a-custom-uid-gid.jpg

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.

Quick Jump: Checking your UID and GID | Custom UID / GID in Your Docker Related Files | Demo Video

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.

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 ARGs 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.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per month (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments