This article helps you running Docker containers without polluting your filesystem with files owned by root.
Most Docker images you find on Docker hub are designed to run as root inside the container. This can lead to your file system to be polluted with files that are owned by root. Here an example, what happens, if you run the official maven image from Docker hub:
drwxr-xr-x 6 root root ...
This may lead to „permission denied“ problems:
Permission denied
Here, we will demonstrate a method of running existing Docker containers as the current user.
Step 1: Dockerfile Template
Let us create a Docker file template.
# Dockerfile.tmpl FROM $FROM_IMAGE RUN groupadd $MYGROUP -g $MYGID \ && useradd -m $MYUSER -u $MYUID -g $MYGID -d /home/user RUN mkdir -p /root/.m2 && chown $MYUSER:$MYGROUP -R /root USER $MYUSER WORKDIR /home/user ENTRYPOINT ["/usr/local/bin/mvn-entrypoint.sh", "mvn"]
The file is the basis for the next step when we create our own Docker Maven image.
Note: the parts in blue are specific for Maven. All other parts are re-usable for other Docker images.
Step 2: Build a local Docker Image
We build a derived Docker image and place it in the local Docker repository:
#!/usr/bin/env bash export FROM_IMAGE=maven:3.6.3-jdk-11-slim export MYUID=$(id -u) export MYGID=$(id -g) export MYUSER=$(id -nu) export MYGROUP=$(id -ng) cat Dockerfile.tmpl | envsubst > Dockerfile docker build -t mvn .
Step 3: Run local Image
We run the local Docker image we just have created with the following script:
#!/usr/bin/env bash
# place this file on /usr/local/bin/mvn and make sure it is executable by everyone
IMAGE=mvn
mkdir -p $HOME/.m2
docker run -it --rm \
-v $(pwd):/pwd \
-v $HOME/.m2:/root/.m2 \
-w /pwd $IMAGE $@
Step 4: Verify the Solution
mvn -version
mvn -version # output: Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: /usr/share/maven Java version: 11.0.6, vendor: Oracle Corporation, runtime: /usr/local/openjdk-11 Default locale: en, platform encoding: UTF-8 OS name: "linux", version: "3.10.0-693.11.6.el7.x86_64", arch: "amd64", family: "unix"
mvn clean verify
git clone https://github.com/oveits/spring-boot-resttemplate-example \ && cd spring-boot-resttemplate-example/initial mvn clean verify
Verify the File’s Ownership
Repository
This should download a lot of data into your home .m2 folder:
ls -l ~/.m2/repository/ # output: total 104 drwxr-xr-x 3 centos centos 4096 Jan 27 23:27 backport-util-concurrent drwxr-xr-x 3 centos centos 4096 Jan 27 23:26 ch drwxr-xr-x 3 centos centos 4096 Jan 27 23:26 classworlds drwxr-xr-x 21 centos centos 4096 Jan 27 23:37 com drwxr-xr-x 3 centos centos 4096 Jan 27 23:27 commons-beanutils drwxr-xr-x 3 centos centos 4096 Jan 27 23:26 commons-cli
Note that all the folders are owned by the current user („centos“ in this case). The repository is not polluted by files and folders owned by root. The same holds for the current project folder:
Current Directory
ls -l # output: total 52 ... drwxr-xr-x 6 centos centos 4096 Mar 14 13:44 target
The target folder and its contents created or updated by Maven are owned by the correct user.
Done.
Alternatives
A.1 Create User and Group at bootup of the Container
Above, we have baked the User ID and Group ID into the Docker image. Thus, the image cannot be used by other users without modification.
A.1.1 Usage
Instead of baking the User and Group into the Docker image, we have created an image that can be used without change like follows:
See also the mvn
file on https://github.com/oveits/dockerfiles/tree/feature/2-maven-with-current-user-bootup/maven-as-current-user.
#!/usr/bin/env bash MVN_AS_USER_IMAGE=oveits/maven-as-user # create local maven repo mkdir -p $HOME/.m2 # start container in the background as root # the entrypoint files create user and group CONTAINER=$(docker run -d --rm \ -e MYUID=$(id -u) \ -e MYGID=$(id -g) \ -e MYUSER=$(id -nu) \ -e MYGROUP=$(id -ng) \ -v $(pwd):/pwd \ -v $HOME/.m2:/home/user/.m2 \ -w /pwd $MVN_AS_USER_IMAGE ) # run the maven command as current user: docker exec -it -u $(id -u) $CONTAINER mvn $@ # cleaning: delete container docker stop $CONTAINER >/dev/null &
You just need to copy this file to a path that is found by the PATH variable (e.g. to /usr/local/bin/mvn
). After this, the command can be used:
mvn -version # output: Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: /usr/share/maven Java version: 11.0.6, vendor: Oracle Corporation, runtime: /usr/local/openjdk-11 Default locale: en, platform encoding: UTF-8 OS name: "linux", version: "3.10.0-693.11.6.el7.x86_64", arch: "amd64", family: "unix"
A.1.2 Background information
A.1.2.1 Dockerfile
The image is created with the following Dockerfile:
# Dockerfile ARG FROM_IMAGE_VERSION=latest FROM maven:$FROM_IMAGE_VERSION COPY 1_entrypoint-user.sh / RUN chmod +x /1_entrypoint-user.sh ENTRYPOINT ["/1_entrypoint-user.sh", "/usr/local/bin/mvn-entrypoint.sh", "mvn"]
A.1.2.2 Entrypoint creating Group and User
It has baked in a versatile entry-point that will create the group and user:
#!/usr/bin/env bash # # 1_entrypoint-user.sh # if [ "$(id -u)" == "0" ]; then groupadd $MYGROUP -g $MYGID \ && useradd -m $MYUSER -u $MYUID -g $MYGID -d /home/user \ && while true; do echo waiting for process to be killed...; sleep 60; done else exec $@ fi
The entry-point file will create the group and user only if the container was started as root with user ID 0. In this case, it will wait until it is killed.
The content of this file will be the same for any image you may want to move from root to your current user.
A.1.2.3 Second, built-in Entrypoint
In case, it was called by a non-root user, it will just start the next entry-point. In our case, this is the original entry-point called in the original maven image, i.e. /usr/local/bin/mvn-entrypoint.sh
.
Note that we have started the container as root in detached mode first:
# mvn ... # start container in the background as root # the entrypoint files create user and group CONTAINER=$(docker run -d --rm \ ... ...
A.1.2.4 Details about the mvn Script
While the docker container is waiting, we start a docker exec command as the current user:
# mvn ... CONTAINER=$(docker run -d --rm \ ... # run the maven command as current user: docker exec -it -u $(id -u) $CONTAINER mvn $@ ...
Finally, we clean the system from the container. We do this in the background, so the user does not have to wait for the cleanup to be finished:
# mvn ... CONTAINER=$(docker run -d --rm \ ... docker exec -it -u $(id -u) $CONTAINER mvn $@ ... # cleaning: delete container docker stop $CONTAINER >/dev/null &
Done.
A.2 Map User ID 0 to another User ID (not tested yet here)
- It seems to be possible to re-map the User IDs between Docker container and Docker host: see the Docker docs. This option is not shown here.
Summary
We have shown how to avoid that Docker containers create files owned by root. For that we have developed two options:
- Creating a docker image on the fly. For that, we have baked the current group and user into the image, before we run the image with the
-u <current_user>
option. The created image cannot be re-used for other users. - Another option is to use an image that creates the user and group on the fly during startup. In this case, the container must be run in a detached mode as root and wait for the user to execute the corresponding command with the
docker exec -u <current_user> ...
In both cases, we have shown, that neither the maven repository nor the project folder is polluted by files with the wrong ownership.
The first option might feel more natural and straightforward. However, the second option is easy to use: you just need to copy the mvn
script to /usr/local/bin
and start using it. There is no need to create additional local Docker images.
Your writing is like a breath of fresh air in the often stale world of online content. Your unique perspective and engaging style set you apart from the crowd. Thank you for sharing your talents with us.
Your blog is a breath of fresh air in the often stagnant world of online content. Your thoughtful analysis and insightful commentary never fail to leave a lasting impression. Thank you for sharing your wisdom with us.
I’ve been following your blog for quite some time now, and I’m continually impressed by the quality of your content. Your ability to blend information with entertainment is truly commendable.
Thank you for your sharing. I am worried that I lack creative ideas. It is your article that makes me full of hope. Thank you. But, I have a question, can you help me?