BoxBoat Blog

Service updates, customer stories, and tips and tricks for effective DevOps

x ?

Get Hands-On Experience with BoxBoat's Cloud Native Academy

Introducing fixuid: Tool for Changing Docker Container UID/GID at Runtime

by Caleb Lloyd | Tuesday, Jul 25, 2017 | Docker

featured.png

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:

FROM node:7.10.0-alpine
RUN yarn global add webpack webpack-dev-server
CMD ["/bin/sh", "-c", "cd /ui && yarn install --no-bin-links && webpack-dev-server"]

The code repository layout looks like this:

-rw-rw-r--   1 alice alice 1.3K Jul 25 09:35 package.json
drwxrwxr-x  11 alice alice 4.0K Jul 25 09:35 src
-rw-rw-r--   1 alice alice 6.8K Jul 25 09:35 webpack.config.js

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:

drwxr-xr-x   2 root  root  4.0K Jul 25 09:39 dist
drwxr-xr-x 541 root  root   20K Jul 25 09:39 node_modules
-rw-rw-r--   1 alice alice 1.3K Jul 25 09:35 package.json
drwxrwxr-x  11 alice alice 4.0K Jul 25 09:35 src
-rw-rw-r--   1 alice alice 6.8K Jul 25 09:35 webpack.config.js
-rw-r--r--   1 root  root  137K Jul 25 09:39 yarn.lock

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:

FROM node:7.10.0-alpine
RUN yarn global add webpack webpack-dev-server
CMD ["/bin/sh", "-c", "cd /ui && yarn install --no-bin-links && webpack-dev-server"]
USER node:node

The fix appeared to work, Alice now owns all of her files on the host:

drwxr-xr-x   2 alice alice  4.0K Jul 25 09:39 dist
drwxr-xr-x 541 alice alice   20K Jul 25 09:39 node_modules
-rw-rw-r--   1 alice alice 1.3K Jul 25 09:35 package.json
drwxrwxr-x  11 alice alice 4.0K Jul 25 09:35 src
-rw-rw-r--   1 alice alice 6.8K Jul 25 09:35 webpack.config.js
-rw-r--r--   1 alice alice  137K Jul 25 09:39 yarn.lock

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:

drwxr-xr-x   2 alice alice  4.0K Jul 25 09:39 dist
drwxr-xr-x 541 alice alice   20K Jul 25 09:39 node_modules
-rw-rw-r--   1 bob   bob   1.3K Jul 25 09:35 package.json
drwxrwxr-x  11 bob   bob   4.0K Jul 25 09:35 src
-rw-rw-r--   1 bob   bob   6.8K Jul 25 09:35 webpack.config.js
-rw-r--r--   1 alice alice  137K Jul 25 09:39 yarn.lock

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:

FROM node:7.10.0-alpine

# install fixuid
RUN USER=node && \
    GROUP=node && \
    curl -SsL https://github.com/boxboat/fixuid/releases/download/v0.1/fixuid-0.1-linux-amd64.tar.gz | tar -C /usr/local/bin -xzf - && \
    chown root:root /usr/local/bin/fixuid && \
    chmod 4755 /usr/local/bin/fixuid && \
    mkdir -p /etc/fixuid && \
    printf "user: $USER\ngroup: $GROUP\n" > /etc/fixuid/config.yml
ENTRYPOINT ["fixuid"]

RUN yarn global add webpack webpack-dev-server
CMD ["/bin/sh", "-c", "cd /ui && yarn install --no-bin-links && webpack-dev-server"]
USER node:node

Alice now passes -u 1000:1000 to the docker run command to start the container using her host UID/GID:

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

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:

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

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:

drwxr-xr-x   2 bob bob  4.0K Jul 25 09:39 dist
drwxr-xr-x 541 bob bob   20K Jul 25 09:39 node_modules
-rw-rw-r--   1 bob bob 1.3K Jul 25 09:35 package.json
drwxrwxr-x  11 bob bob 4.0K Jul 25 09:35 src
-rw-rw-r--   1 bob bob 6.8K Jul 25 09:35 webpack.config.js
-rw-r--r--   1 bob bob  137K Jul 25 09:39 yarn.lock

fixuid is an open source project sponsored by BoxBoat – check out the repository for more detailed installation instructions and use cases!