BoxBoat Blog
Service updates, customer stories, and tips and tricks for effective DevOps
Introducing fixuid: Tool for Changing Docker Container UID/GID at Runtime
by Caleb Lloyd | Tuesday, Jul 25, 2017 | Docker
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!