Docker run: Mirror Host User

Docker makes it easy to share host network and filesystem, but it doesn't make it easy to share the host's user ID and group ID. This is very useful when using a container locally via docker run, when it needs to write files to a volume shared with the host. In that case it is useful for the files to have the same user ID and group ID as the user on host.

Currently the way I do this is as follows:

  • In the Dockerfile, I create a user runuser and group rungroup via useradd and groupadd commands, and I set final USER so container defaults to that user:

RUN groupadd rungroup \
&& useradd -ms /bin/bash -g rungroup runuser
USER runuser
...more setup...
# ensure container runs as runuser
USER runuser
  • In a small script, run the docker container in detached mode (--detach)

docker run \
...
--env USER \
--name $CTNR_NAME \
--rm \
--detach \
--tty \
IMAGE_NAME
  • Then docker exec usermod and groupmod to match the host's user ID and group ID:

docker exec -it -u root $CTNR_NAME groupmod -g "$GROUP_ID" rungroup
docker exec -it -u root $CTNR_NAME usermod -u $UID runuser
  • A final line in the script executes the desired command in the container, such as a shell. The command will run as runuser:rungroup but with the ID that match that of the host:

# runs shell as last USER in Dockerfile
docker exec -it $CTNR_NAME id
  • If you need a root shell in container, say to install more apps temporarily (because if you restart the container those apps will be gone -- which is a good thing, ensure clean slate for any new container), change user:

docker exec -it -u root $CTNR_NAME /bin/bash

This is quite tricky and required a fair bit of time to figure out.

I've seen another solution of mounting /etc/passwd and /etc/group but this exposes way more info in the container than necessary (which is just one line of each file). So for me this is not a solution.

In any case the above enables multiple simultaneous shells, each one running as either root or runuser, in latter case the UID and GROUP_ID will match that of host user who started the container, and all shells can be exited without terminating the container.

One caveat is that there will still be files owned by the original user ID that got created in the Dockerfile. Eg if the useradd command in Dockerfile created user runuser with ID 2000, and then in Dockerfile other commands are run as that user that creates files, the files will have ownership by user ID 2000. The docker exec that is run later changes the runuser ID to something else, but this does not change the ownership of any files already created. Therefore, you may need to chown those files via an additional docker exec. Eg

docker exec -it -u root $CTNR_NAME \
chown runuser /var/run/docker.sock

In the small script I additionally have a check to determine if the container is already running, in that case it skips the docker run, and also to easy choose between runuser and root:

#!/usr/bin/env bash
run_as_root=false
if [[ ${1:-} == '--su' ]]; then
shift
echo "Will run as root"
run_as_root=true
fi
CTNR_NAME=something
if [[ -z $( docker ps -qf name=$CTNR_NAME ) ]]; then
echo "Starting new container $CTNR_NAME"
docker run \
...
# match host user ID and group ID
docker exec -it -u root $CTNR_NAME groupmod -g "$GROUP_ID" rungroup
docker exec -it -u root $CTNR_NAME usermod -u $UID runuser
# some files need to be re-owned by runuser
docker exec -it -u root $CTNR_NAME chown runuser /var/run/docker.sock
else
echo "Container $CTNR_NAME is already running"
fi
echo "Shelling into $CTNR_NAME container"
if [[ $run_as_root == true ]]; then
docker exec -it --user root $CTNR_NAME /bin/bash
else
docker exec -it --user runuser $CTNR_NAME /bin/bash
fi