Skip to main content

Running Containers as a Non-Root User: A Complete Guide

Bibin Wilson

In this blog, we will learn how to run containers as a non-root user by configuring Dockerfiles, managing permissions, and addressing real-world application challenges.

To run a container as a non-root user, you just need to create and add the USER instruction specifying the non-root user.

RUN groupadd -g 1001 flaskgroup && \
    useradd -u 1001 -g flaskgroup -m flaskuser

USER flaskuser

In real-world projects, it's more than just this.

You must understand how the application and its configuration work. Also, you need to ensure that the non-root user can handle all application requirements, such as reading and writing to files or accessing configurations.

Host root vs. Container root

Host root and container root are not the same in terms of host resource access.

Let’s break this down with some examples to make the difference clearer.

Host Root User

The root user on the host system has complete control over all resources and processes on that system. The host root can access any files, run any commands, install or uninstall software, and change system configurations.

Essentially, they are the ultimate administrator of the host.

Container Root User

The root user inside a container has similar privileges within the container.

Even though the container root user appears to be like the host root user, it is isolated in many ways.

Note: Container root (UID 0) is the same as host UID 0 unless user namespaces are explicitly configured.

This means that while it has root privileges within the container, it does not have direct access to the host system's resources due to Namespace, Control groups, and security profiles like seccomp.

This means that the container runtime removes some special permissions (called capabilities) and blocks specific system calls (using seccomp) to make the container environment more secure, even though the user ID (UID) remains the same inside the container and on the host.

For example,

$ docker run -it ubuntu

root@0295ecbcec17:/# mount -t tmpfs none /mnt
mount: /mnt: permission denied.
       dmesg(1) may have more information after failed mount system call.

root@0295ecbcec17:/# dmesg
dmesg: read kernel buffer failed: Operation not permitted

However, if you explicitly allow privileges through capabilities or the --privileged flag, the container root will have access to the host (e.g., through volume mounts or network configurations).

You can directly mount host filesystems, change host kernel parameters, write to system files, and access or modify them.

For example,

# Explicitly allowing access to the host filesystem

$ docker run --privileged -it ubuntu

root@ff566015378b:/# mount /dev/sda1 /mnt

root@ff566015378b:/# cd /mnt/

root@ff566015378b:/mnt# ls
EFI

root@ff566015378b:/# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

root@ff566015378b:/# echo 1 > /proc/sys/net/ipv4/ip_forward

Now, let’s say you are running a container without privileged mode.

However, there is still a risk: if a container running as root is compromised, vulnerabilities may allow an attacker to break out of the container and gain host-level access.

This is why running containers as root is strongly discouraged

Running Containers as Non-Root User

Now, let's look at what you need to know to run containers as non-root users.

Creating a non-root user

The first step is to create a non-root user.

The groupadd and useradd commands create a new user (flaskuser) and group (flaskgroup) with a fixed UID and GID (e.g., 1001 - commonly used as a UID for a non-root user).

Note: UIDs 1000 and above are generally assigned to non-root users (regular users). Many Linux distributions assign UIDs starting from 1000 or 1001 to non-root users by default. The UID 0 is reserved for the root user. You can check these ranges on your specific system by looking at the /etc/login.defs

For example,

RUN groupadd -g 1001 flaskgroup && \
    useradd -u 1001 -g flaskgroup -m flaskuser

File permissions

When you run applications, they often expect to write to specific directories. It could be a log file or a runtime configuration file.

You need to ensure proper ownership and permissions are set during the image build process.

For example, let's say your app wants to write logs to /app/logs. In that case, your non-root user configuration would look like this:

RUN mkdir -p /app/logs && \
chown flaskuser:flaskgroup /app/logs && \
chmod 755 /app/logs

Port binding

Non-root users can't bind to privileged ports (ports < 1024).

Services like Nginx, by default, accept connections on port 80. Additionally, if you check the standard Nginx images, you will find that they are configured to run as root by default.

For example, if you run a Nginx container and check its processes, you can see that the master process is running as root.

$ docker run -d --name nginx nginx

f5896b56bbbdcca7d8....

$ docker top nginx | awk '{print $1, $8, $9}'

UID CMD 
root nginx: master
systemd+ nginx: worker

Now, if you want to run apps like these as non root., you need to change the default port to another port, let's say 8080, during the build time.

For example,

RUN sed -i 's,listen       80;,listen       8080;,' /etc/nginx/conf.d/default.conf
Note: If you want to configure the application to use lower ports (ports < 1024), although highly discouraged, you need to use capabilities like CAP_NET_BIND_SERVICE (which should be used cautiously) without giving full root access or running the entire container as root

Service Dependencies

Let’s say you want to containerize an application that assumes it can write logs to a system directory like /var/log/ (which requires root).

Additionally, it communicates with another process through /var/run/ for inter-process communication (IPC), which also requires root.

In these cases, you might want to configure the application or refactor it to write to different locations. For example:

  • Logs are written to /app/logs instead of /var/log.
  • IPC files are created in /app/run instead of /var/run.

In the Dockerfile, the application runs as appuser, a non-root user created during the build process. Ownership of /app/logs and /app/run is granted to appuser.

For example:

ENV APP_USER=appuser
ENV LOG_DIR=/app/logs
ENV RUN_DIR=/app/run

# Create a non-root user
RUN useradd -ms /bin/bash $APP_USER

# Create alternative directories for logs and runtime files
RUN mkdir -p $LOG_DIR $RUN_DIR \
    && chown -R $APP_USER:$APP_USER $LOG_DIR $RUN_DIR

Configuration Files & Runtime directories

There are applications that often assume root access and expect to create or modify directories in locations requiring root privileges.

For such applications, you might need to:

  1. Allow non-root user access to those directories or files.
  2. Pre-create directories with the correct permissions.
  3. Configure alternative paths for files and directories.

The Nginx Unprivileged Dockerfile is a classic example of this scenario. It is configured to run Nginx as a non-root user.

For example,

Example Flask App Non-Root Dockerfile

Here is an example Dockerfile that uses a non-root user for your reference:

It changes ownership of /app to a user with:

  • UID: 1001
  • GID: 1001

This prepares the directory for use by the non-root user.

It then switches to the non-root user with UID 1001 for subsequent commands.

FROM python:3.11-slim

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Create a non-root user with UID 1001 and GID 1001, and set permissions
RUN mkdir -p /app && \
    chown -R 1001:1001 /app

# Switch to the non-root user with UID 1001
USER 1001:1001

COPY --chown=1001:1001 requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY --chown=1001:1001 . .

EXPOSE 5000

CMD ["python", "app.py"]

Container non-root Vs. Kubernetes Non-root User

Now, let's say you have a non-root user configured in the container.

What happens when you deploy it in a Kubernetes pod?

How does Kubernetes treat the non-root user configured in the container?

Here is a detailed guide about Kubernetes Pods With SecurityContext.

If you have any questions about this blog, drop them in the comments!