Docker requires root escalation in order to execute an image, that crates some problem with files creation. Let’s say that we share a volume from host to docker and we create a file structure from inside docker.

This can be illustrated by an code snippet

docker run --rm -w $(pwd) -v $(pwd):$(pwd) debian \
    bash -c "mkdir test && touch test/example"
# Running on host
ls -la test

 total 8
 drwxr-xr-x.  2 root   root   4096 Jan 22 22:50 .
 drwxrwxr-x. 12 trzeci trzeci 4096 Jan 22 22:50 ..
 -rw-r--r--.  1 root   root      0 Jan 22 22:50 example
# Running on host
rm -fr test
 rm: cannot remove 'test/example': Permission denied

Why this is even an issue? I’ve found this problem mostly annoying on local setup, as I need to use sudo for simple hose-keeping tasks. Another case when it’s very problematic is to use docker on Jenkins – as this prevents to remove workspace after a job is done.

#1 – Run docker as a specific user

#1.1 Simple case

Docker can be run with -u switch that enables user mapping, which is kind of cool as we can basically access mounted volumes as ourselves.

docker run --rm \
  -w $(pwd) \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  debian bash -c "mkdir test && touch test/example"
# Running on host
ls -la test

 total 8
 drwxr-xr-x.  2 trzeci trzeci 4096 Jan 22 22:53 .
 drwxrwxr-x. 12 trzeci trzeci 4096 Jan 22 22:53 ..
 -rw-r--r--.  1 trzeci trzeci    0 Jan 22 22:53 example

rm -fr test
# Works fine!

Looks like everything works. Right? There is some caveat, as we user hosts UID and GID, that might be not reflected inside docker image, we’re loosing $HOME folder

#1.2 Case of missing HOME

docker run \
  --rm \
  -w $(pwd) \
  -v $(pwd):$(pwd) \
  -u $(id -u):$(id -g) \
  debian bash -c "echo HOME:\$HOME \$(id) - \$(id -un)"

 id: cannot find name for user ID 1000
 HOME:/ uid=1000 gid=1001 groups=1001 - 1000

This issue is problematic, because if we’ll run for the instance npm install command, then npm will try to create a file inside ~/.npm path, which effectively is ${HOME}/.npm and because HOME is empty as there might be no user with requested id, it will be resolved to /.npm which eventually leads to permission denied error.

Even the official node image has this problem driven by an assumption that every base linux user is 1000:1000

# https://github.com/nodejs/docker-node/blob/86b9618674b01fc5549f83696a90d5bc21f38af0/8/jessie-slim/Dockerfile#L3-L4
RUN groupadd --gid 1000 node \
&& useradd --uid 1000 --gid node --shell /bin/bash --create-home node

And it can be reproduced simply with following script

#!/bin/bash

mkdir -p test_docker_node
cd test_docker_node
docker run -v `pwd`:/src -w /src -u 2000:2000  --rm -it node:11.10-slim \
    bash -c "npm install request --save" 

And output is:

Unhandled rejection Error: EACCES: permission denied, mkdir '/.npm'2ad9274b6c834
npm ERR! cb() never called!
npm ERR! This is an error with npm itself. Please report this error at:
npm ERR!     <https://npm.community>

Where if we use -u 1000:1000 it work as expected:

docker run -v `pwd`:/src -w /src -u 1000:1000  --rm -it node:11.10-slim \
    bash -c "npm install request --save" 

npm WARN saveError ENOENT: no such file or directory, open '/src/package.json'
npm WARN enoent ENOENT: no such file or directory, open '/src/package.json'
npm WARN src No description
npm WARN src No repository field.
npm WARN src No README data
npm WARN src No license field.

+ request@2.88.0
updated 1 package and audited 188 packages in 1.304s
found 0 vulnerabilities

This solution adds some extra work on maintainer:
* A user has to be created by Dockerfile (i.e. touch ~/.test doesn’t work on debian image with -u 1000:1000 as no user is created
* Every folder that possible can be used to write information, has to be non-root friendly. One example what I faced was that Emscripten was using a cache folder inside installation of Emscripten.

#1.3 Making a fake home

This can be achieved simply by setting -e HOME=/tmp for running container. Then following example will work even if your local userId isn’t 1000

#!/bin/bash
mkdir -p test_docker_node
cd test_docker_node
docker run -v `pwd`:/src -w /src -u `id -u`:`id -g` -e HOME=/tmp --rm -it node:11.10-slim \
    bash -c "npm install request --save" 

rm -fr test_docker_node

Other test case which will work now

docker run --rm -it -v `pwd`:/src -w /src -u `id -u`:`id -g` -e HOME=/tmp debian \
    bash -c "touch  ~/.test" 

#2 – Use umask

This is slightly more intrusive, as either requires to prepend commands that run in docker, create custom image (that derives from an original image).
What’s needed is to add umask 0000

#2.1 Prepend executed command

docker run \
  --rm \
  -w $(pwd) \
  -v $(pwd):$(pwd) \
  debian bash -c "umask 0000 && mkdir test && touch test/example"
ls -la test

 total 8
 drwxrwxrwx.  2 root   root   4096 Jan 22 23:01 .
 drwxrwxr-x. 12 trzeci trzeci 4096 Jan 22 23:01 ..
 -rw-rw-rw-.  1 root   root      0 Jan 22 23:01 example

rm -fr test
# Works fine!

#2.2 Create custom Dockerimage

Content of entrypoint.sh

#!/bin/bash
umask 0000
"$@"

Content of Dockerfile

FROM debian
COPY ./entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

Although it looks fairly simple, it doesn’t cover all cases. umask forks only for newly created file, if a file has been copied/moved then we have to support it in a different way.

#2.3 Ugly case of copying folders

The base issue is that cp command preserves access mask but changes owner to the current user. Effectively what it does mean is that files copied while being a docker inside mounted volume getting root as an owner. For files it might be not a big issue as we might still be able to modify / remove such files if other permission mask is allows us.

Unfortunately this is not a case for copied folders.

Here is an example that illustrates this issue:

#!/bin/bash

mkdir -p test_folder/sub_folder

cd test_folder
touch ./sub_folder/test_file.txt
docker run -v `pwd`:/src -w /src --rm -it debian \
    bash -c "umask 0000 && cp -r sub_folder sub_folder_docker" \
    && ls -la
cd ..

rm -fr test_folder

Result from running it:

./test_current_user.sh 
total 16
drwxrwxr-x. 4 trzeci trzeci 4096 Feb 26 22:27 .
drwxrwxr-x. 4 trzeci trzeci 4096 Feb 26 22:27 ..
drwxrwxr-x. 2 trzeci trzeci 4096 Feb 26 22:27 sub_folder
drwxrwxr-x. 2 root   root   4096 Feb 26 22:27 sub_folder_docker
rm: cannot remove 'test_folder/sub_folder_docker/test_file.txt': Permission denied

It is possible to make it run of course, simple chmod -R 777 solves it. But this hack can’t be used everywhere as it might significantly impact performance in case of large volumes mounted. Which is a case of mine, where I use Docker for compilation of large C++ projects.

Summary

Making Docker container to produce files inside mounted volume that don’t require root for removing isn’t easy task and frequently requires that wanted Docker image needs to be constructed in such a way that allows to use it as non-root user.

Frequently Dockerfiles don’t create a standard user, and even if they do, we shouldn’t assume that created user inside Docker Image has the same UID and GID like user on host machine. If they don’t, it’s possible to override HOME environment variable to trick applications that use ~ for storing some cache/settings files.

Running Docker Image as root but setting umask isn’t a perfect solution as when copying of folders inside mounted volume is performed by an operation inside running docker, then root becomes an owner of it, which will cause permission denied, when trying to remove it as a local non-root user.

 
4 Kudos
Don't
move!