The problem
Docker makes local environment same as CI and production, right? Well, in theory. Recently we had a single test failing in CI (dockerized CircleCI v2) but passing in a Docker container running locally. What the hell!
The test basically checked file permissions. We were creating a folder, then changing its permission to disallow listing its contents, and then made an attempt to list the contents. We were expecting the attempt to fail - and when running in plain Mac OSX terminal it did.
1 | // the setup |
We placed this code inside a Docker image using the following Dockefile. You can find the example code in repo bahmutov/docker-file-permissions
- the problematic Dockerfile is in branch
problem
.
1 | FROM node:6 |
I built the container and ran it
1 | > docker run --name perms -it gleb/docker-file-permissions /bin/bash |
We can list the current user in the container (which is root
with id 0)
and the permissions on the folder foo
. I am using commands
id
, ls -ld
and stat
present on Linux and Mac to inspect user and file
details.
1 | root@97d0341957e8:/usr/src/app# id |
So we see that we are user with id=0
from group gid=0
and the file
belongs to this user and group. Despite changed permissions, the folder is
still accessible.
The solution
After reading about Docker treats user ids I changed the Docker container to have new non-root user. This is a good Docker security practice in general. The Dockefile required only a few changes:
- Create a new user
person
and switch to it - Copy and install app inside the new user's home folder.
1 | FROM node:6 |
If we had global NPM installs, we would need to give user person
permissions
to /usr/local/node
folder. In our example everything was local (as it should
be in general).
Rebuilding the image and running the container catches the expected exception
1 | person@d8ea300df4f4:~/app$ node . |
Inspecting the user and the folder shows the non-root owner
1 | person@d8ea300df4f4:~/app$ id |
Glad it is working!
Update 1
Geoff Goodman has pointed out that our problem
was not with user namespaces (how Docker can map internal user to external
user), but with root
user permission. In fact, he asked, how could our
local Docker test pass when we were running as root?!
The full picture: in order to save time and avoid building Docker image with
full source code, we only build the run environment and mapped source folder
as data volume at runtime. The Dockerfile has nothing, just plain Node image
(I pushed this code as mapped
branch)
1 | FROM node:6 |
Running container with mapped current folder brings source
1 | $ docker run --name perms -v $PWD:/usr/src/app -it gleb/docker-file-permissions /bin/bash |
1 | root@50415a48be51:/usr/src/app# node . |
So running as root
with mapped data volume on Mac OSX
(Docker 17.06.0-rc1-ce-mac13) hides the problem, which becomes apparent
when running Docker on true Linux box.
Update 2
Same Geoff Goodman also reminded me that Node Docker image has
a non-root user already named node
(surprise, surprise).
I could have used it and not create my own person
. In my Docker file I could
have switched to this user with USER node
or when running the container
-u node
- this is assuming we gave this user permission to create folders
in the container.