Switching Users Inside Docker Image to a Non-Root User

Switching users inside Docker image to a non-root user

You should not use su in a dockerfile, however you should use the USER instruction in the Dockerfile.

At each stage of the Dockerfile build, a new container is created so any change you make to the user will not persist on the next build stage.

For example:

RUN whoami
RUN su test
RUN whoami

This would never say the user would be test as a new container is spawned on the 2nd whoami. The output would be root on both (unless of course you run USER beforehand).

If however you do:

RUN whoami
USER test
RUN whoami

You should see root then test.

Alternatively you can run a command as a different user with sudo with something like

sudo -u test whoami

But it seems better to use the official supported instruction.

Docker - is it safe to switch to non-root user in ENTRYPOINT?

I just looked through what relevant literature (Adrian Mouat's Docker, Liz Rice's Container Security) has to say on the topic and added my own thoughts to it:

The main intention behind the much cited best practice to run containers as non-root is to avoid container breakouts via vulnerabilities in the application code. Naturally, if your application runs as root and then your container has access to the host, e.g. via a bind mount volume, a container breakout is possible. Likewise, if your application has rights to execute system libraries with vulnerabilities on your container file system, a denial of service attack looms.

Against these risks you are protected with your approach of using runuser, since your application would not have rights on the host's root file system. Similarly, your application could not be abused to call system libraries on the container file system or even execute system calls on the host kernel.

However, if somebody attaches to your container with exec, he would be root, since the container main process belongs to root. This might become an issue on systems with elaborate access right concepts like Kubernetes. Here, certain user groups might be granted a read-only view of the cluster including the right to exec into containers. Then, as root, they will have more rights than necessary, including possible rights on the host.

In conclusion, I don't have strong security concerns regarding your approach, since it mitigates the risk of attacks via application vulnerabilities by running the application as non-root. The fact that you run to container main process as root, I see as a minor disadvantage that only creates problems in niche access control setups, where not fully trusted subjects get read-only access to your system.

Building Docker image as non root user

In order to use Docker, you don't need to be a root user, you just need to be inside of the docker user group.

On Linux:

  1. If there is not already a docker group, you can create one using the command sudo groupadd docker.
  2. Add yourself and any other users you would like to be able to access docker to this group using the command sudo usermod -aG docker [username of user].
  3. Relog, so that Linux can re-evaluate user groups.

If you are not trying to run the command as root, but rather want to run the container as non-root, you can use the following DOCKERFILE contents (insert after FROM but before anything else.)

# Add a new user "john" with user id 8877
RUN useradd -u 8877 john
# Change to non-root privilege
USER john

Should I run things inside a docker container as non root for safety?

Running the container as root brings a lot of risks. Although being root inside the container is not the same as root on the host machine (some more details here) and you're able to deny a lot of capabilities during container startup, it is still the recommended approach to avoid being root.

Usually it is a good idea to use the USER directive in your Dockerfile after you install some general packages/libraries. In other words - after the operations that require root privileges. Installing sudo in a production service image is a mistake, unless you have a really good reason for it. In most cases - you don't need it and it is more of a security issue. If you need permissions to access some particular files or directories in the image, then make sure that the user you specified in the Dockerfile can really access them (setting proper uid, gid and other options, depending on where you deploy your container). Usually you don't need to create the user beforehand, but if you need something custom, you can always do that.

Here's an example Dockerfile for a Java application that runs under user my-service:

FROM alpine:latest
RUN apk add openjdk8-jre
COPY ./some.jar /app/
ENV SERVICE_NAME="my-service"

RUN addgroup --gid 1001 -S $SERVICE_NAME && \
adduser -G $SERVICE_NAME --shell /bin/false --disabled-password -H --uid 1001 $SERVICE_NAME && \
mkdir -p /var/log/$SERVICE_NAME && \
chown $SERVICE_NAME:$SERVICE_NAME /var/log/$SERVICE_NAME

EXPOSE 8080
USER $SERVICE_NAME
CMD ["java", "-jar", "/app/some.jar"]

As you can see, I create the user beforehand and set its gid, disable its shell and password login, as it is going to be a 'service' user. The user also becomes owner of /var/log/$SERVICE_NAME, assuming it will write to some files there. Now we have a lot smaller attack surface.

Switching Between Root and Non-Root Users in Docker

The typical tool for this in is gosu. When included in your container, you'd run gosu postgres $cmd where the command is whatever you need to run. If it's the only command you need to have running in the container at the end of your entrypoint script, then you'd exec gosu postgres $cmd. The gosu page includes details of why you'd use their tool, the main reasons being TTY and signal handling. Note the end of their readme also lists a few other alternatives which are worth considering.

Dockerfile - Creating non-root user almost doubles the image size

Don't chown the directory; leave root owning it. You also shouldn't need to set a shell, or a password, or create a home directory; none of these things will be used in normal operation.

I'd suggest creating the user towards the start of the Dockerfile (it is fairly fixed and so this step can be cached) but only switching USER at the very end of the file, when you're setting up the metadata for how to run the container.

A Node-based example:

FROM node:lts # debian-based

# Create the non-root user up front
RUN adduser --system --group --no-create-home newuser

# Copy and build the package as usual
WORKDIR /app
COPY package.json yarn.lock .
RUN yarn install
COPY . .
RUN yarn build

# Now the application is built
# And root owns all the files
# And that's fine

# Say how to run the container
EXPOSE 3000
USER newuser
CMD yarn start

Having root owning the files gives you a little extra protection in case something goes wrong. If there's a bug that allows files in the container to be overwritten, having a different user owning those files prevents the application code or static assets from being inadvertently modified.

If your application needs to read or write files then you could create a specific directory for that:

 # Towards the end of the file, but before the USER
RUN mkdir data && chown newuser data

This will let the operator mount some storage over the otherwise-empty directory. This is the only thing that has the newly created user ID in it at all, so if the storage comes with its own owner it shouldn't be an operational problem; you need to also specify the matching user ID at container startup time.

docker run -u $(id -u) -v $PWD/data:/app/data ...


Related Topics



Leave a reply



Submit