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 hause-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 use 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 works 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
works 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.