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:

  1. 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.
  2. 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.

Comments

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.