One of the most helpful things about using Docker containers for development is that it reduces developer onboarding time from a few days to a few hours or less. Developers are able to clone a repository, start a container or run Docker compose, and start contributing immediately.

Development containers are often very different from production containers. They usually include package managers, build tools, SDKs, remote debugging, and more. Source code can be mounted into the development container via a host mount and changes can be immediately re-rendered via live-reload build tools.

This is where the road gets bumpy – Docker containers run as a single user. Users/groups, UIDs/GIDs, and file ownership must be decided when an image is built with docker build.

Host volumes, however, are owned by a user on the host and the host user’s UID may or may not match the container user’s UID. There’s an issue in the Moby repository with over 100 comments about this very topic.

Host volumes are mounted using bind mounts in Linux. There is no way to remap UIDs/GIDs using bind mounts so often development containers end up with a mismatch of UIDs/GIDs. This is why we created fixuid, a tool to change a Docker container’s user/group and file permissions that were set at build time to the UID/GID that the container was started with at runtime.

To explain how fixuid solves this problem, let’s take a look at a story about Alice and Bob, who are both developers working with development Docker containers.

A Basic Development Container

Alice has created a a great Docker container that packages all necessary dependencies to run her nodejs application. Let’s take a look at the Dockerfile:

Alice runs her development Docker container, mounting her code repository into the container:

docker run -d --name app-ui -v $(pwd):/ui -p 3000:3000 app-ui-image

The container comes up and Alice can see her app running on port 3000! Changes to her source code are immediately re-rendered inside the container by webpack-dev-server. But when Alice looks at the files in the code repository on her host, she sees:

The 3 new items here – dist, node_modules, and yarn.lock – are all owned by root:root. This means that Alice cannot make changes to these files or remove them from her host without root permissions.

Running the Container as a non-root User

Alice decides to try and remedy the ownership mismatch by matching the container’s UID/GID to her UID/GID. Alice is running as UID/GID 1000:1000 on her host and notices that the container also has a user/group called node:node with the same UID/GID. She modifies the Dockerfile:

Sharing with Bob

Alice commits her changes and eagerly summons her co-worker, Bob, to teach him how to run the development container. She creates a user account for Bob on her host with UID/GID 1001:1001 and clones a new copy of the repository into his home directory.

Bob watches in awe as the development container with all packaged dependencies serves their app on port 3000. But when Bob inspects the repository under his home directory on the host, he sees:

That doesn’t look right – dist, node_modules, and yarn.lock are all owned by alice:alice on the host.

This has happened because the UID/GID of node:node in the container matches the UID/GID of alice:alice on the host.

Enter fixuid

After some quick searching, Alice and Bob discover fixuid, which can dynamically change the UID/GID of the node:node user/group when their development container is started with docker run. They make one last edit to their Dockerfile:

Since the node:node user/group is already set to UID/GID 1000:1000 in the container, fixuid does not need to change the UID/GID:

fixuid: fixuid should only ever be used on development systems. DO NOT USE IN PRODUCTION
fixuid: runtime UID '1000' already matches container user 'node' UID
fixuid: runtime GID '1000' already matches container group 'node' GID

Bob now passes -u 1001:1001 to the docker run command to start the container using his host UID/GID:

fixuid changes the node:node user/group in the container to UID/GID 1001:1001. It also updates ownership of all of the files in the container owned by node:node to the new UID/GID and fixes the $HOME environment variable.

fixuid: fixuid should only ever be used on development systems. DO NOT USE IN PRODUCTION
fixuid: updating user 'node' to UID '1001'
fixuid: updating group 'node' to GID '1001'
fixuid: chown /home/node

Now, when Bob inspects the repository under his home directory on the host, all of the files created by the container have been properly created using his UID/GID: