After ditching MacOS and moving to Linux I noticed that my dockerized app (in short: app) started to generate files owned by
root user. First, it was annoying (limited access to logs etc), then I started to realize that’s something is wrong.
The answer seemed to be obvious - the owner of app process was
root so the files generated by it were also owned by it. Yet, this didn’t happen back in the MacOS days. What changed?
Here, I should add a quick note about
GID. UID is an identifier of a user, GID of a group. Each user has a UID and GID of a primary group. Every file has also it’s own UID and GID. This defines who owns a file.
So what changed? Well, apparently Docker on Linux transfers ownership of files created in a container.
Since default user in Docker containers is usually
root everything created by the application is owned by it. This could’ve explained why suddenly I had files owned by root.
First, I wanted to change something in Docker (hey, it worked on MacOS!). Sadly it’s not possible. The solution seems to be simple - create an unprivileged user (in Docker image) and make sure that it has the same UID and GID as the user who owns files on the host machine.
This should be a simple task. It’s not.
General structure of Dockerfile
I decided to split my
Dockerfile into two sections.
System section is responsible for installing system wide dependencies (eg.
glib). It’s used also to set up proper permissions for an app user.
App section is responsible for installing project dependencies (eg.
npm i or
yarn), or generating app-specific files (eg. generating API docs).
FROM alpine ### System # Create app user RUN mkdir -p /usr/src/app \ && addgroup app \ && adduser -h /usr/src/app -G app -D app ### App WORKDIR /usr/src/app USER app COPY --chown=app . /usr/src/app/ RUN npm i CMD ["my-app"]
You probably noticed
COPY statement. By default, added files are owned by
--chown=app will change the owner of created files.
Matching UID and GID for dev environment
Having unprivileged user won’t solve entirely permissions problem.
app user will have pairs of UID and GID assigned by the system. It’s possible that they won’t match UID and GID of a user owning files on the host machine.
This means that files generated on a container will have still invalid ownership.
$ docker run --rm -it -v "$PWD:/host" alpine sh / # adduser -D -u 9000 app / # touch /host/test / # chown app /host/test / # exit $ ls -lah test -rw-r--r-- 1 9000 root 0 01-08 19:14 test
Here, I’ve done a couple of things:
- started Docker container with current directory mounted as
/host/testfile and owned it to
- listed files on the host machine
As you can see, the created file is owned by an unknown user (hence the number instead of a name).
To fix that Docker needs to know your users UID and GID. This will ensure that files created by
app user will belong to you on the host machine.
FROM alpine ARG DOCKER_UID=1000 ARG DOCKER_GID=1000 ### System # Create app user RUN mkdir -p /usr/src/app \ && addgroup -g $DOCKER_GID app \ && adduser -h /usr/src/app -u $DOCKER_UID -G app -D app
To build image on host machine run:
docker build \ --build-arg DOCKER_UID=$(id -u) \ --build-arg DOCKER_GID=$(id -g) \ .
The user created inside a Docker image will have UID and GID matching your user. This way files created by the application user will be fully owned by you.
Using existing unprivileged users
Some images (eg. node) provide an unprivileged user (in case of node images it’s…
If you want to use it, make sure that it has matching UID and GID. You can change them using
FROM node:alpine ARG DOCKER_UID=1000 ARG DOCKER_GID=1000 RUN apk add --no-cache -t .deps shadow \ && usermod -u $DOCKER_UID node \ && groupmod -g $DOCKER_GID node \ && apk del .deps
“Fixing” ownership on running containers
This is a tricky problem. If you decide to change the default user in an app which is already deployed you will have to deal with files stored in volumes. Those files will be still owned by the previous user.
This can be fixed in one of two ways:
- manually change ownership of volume files - this requires access to the server; has to be done once
- “fix” ownership everytime a new container starts - can be done without access to the server; complicates things a bit
Here, I’ll focus only on the second step.
Docker has something called
ENTRYPOINT. It’s a script which runs every time a command is run on the container. It’s executed after volumes are mounted so it gives a great opportunity to “fix” ownership of files.
The script has to be run by the
root user. With
USER app statement in
Dockerfile it won’t be possible. It has to be removed. Unfortunately, without it the application will be run by the
root. To prevent this we can use gosu which is an elegant alternative for
sudo. It will make sure that application will be run by a selected user.
entrypoint.sh, a simple three-liner, is responsible for changing ownership of all files in
/usr/src/app and executing
CMD as the
#!/bin/sh chown -R app:app /usr/src/app exec gosu app "$@"
Copy this file to image and set as an entry point. Don’t forget to make it executable.
FROM alpine RUN apk add --no-cache \ --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing \ gosu COPY entrypoint.sh /entrypoint.sh ENTRYPOINT ["/entrypoint.sh"]
Access to reserved ports
Ports 0-1024 are reserved to privileged services. Unprivileged users can’t open them by default.
It’s possible to allow a selected binary to open such ports. To do this you need to use
apk add --no-cache libcap setcap 'cap_net_bind_service=+ep' $(which my-app)
Problems with access to
There’s an issue with writing to
/dev/stderr (both descriptors are used by Docker to catch the output and store it in logs).
$ docker run --rm -it alpine sh / # adduser -D app / # su - app bfa7868d4ff8:~$ ls / > /dev/stdout -sh: can't create /dev/stdout: Permission denied
There are few ways to fix it. In my case they worked on the local machine, however, on Rancher it didn’t work out.
Unfortunately, this can lead to limited application output. In my case, I was unable to redirect output of supervisor process to
Surprisingly, my node application could write to
stdout without any problems.
I didn’t mention any security concerns. I’m not an expert, yet as a rule of thumb, it’s better to run the application by an unprivileged user (this includes also dockerized apps). Make sure that, if possible, your app is owned by an unprivileged user.
If you decide to do so - make sure that application files are owned by the selected user and the application has correct access to things it requires.