Docker Non Root User

Containers run as the root user by default. Root is the all-powerful administrator account on Linux systems. A program that runs as root inside a container carries far more power than it needs, and a flaw in that program hands an attacker the same power. Running as an ordinary user shrinks the damage.

The Hotel Key Analogy

A hotel gives guests room keys and gives only a few staff members the master key. Imagine every guest receiving a master key. One lost key would open every door. A non-root user works like a room key: it opens exactly what the program needs and nothing more.

Root user        master key   opens everything
Non-root user    room key     opens only the program's own folders

Checking the Current User

docker run --rm alpine whoami
root

docker run --rm alpine id
uid=0(root) gid=0(root)

User ID 0 always means root.

Creating a User in a Dockerfile

The USER instruction switches the account for all later instructions and for the running container.

FROM node:20-alpine
WORKDIR /app

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

COPY --chown=appuser:appgroup package*.json ./
RUN npm install --omit=dev
COPY --chown=appuser:appgroup . .

USER appuser
EXPOSE 3000
CMD ["node", "server.js"]

Debian and Ubuntu based images use slightly different commands.

RUN groupadd --system appgroup && useradd --system --gid appgroup appuser

File Ownership Matters

A new user cannot write to folders owned by root. Use COPY --chown to hand files to the right owner as Docker copies them. Without it, the application may crash with a "permission denied" error when it tries to write logs or temporary files.

Folder owner: root      appuser tries to write  -> permission denied
Folder owner: appuser   appuser tries to write  -> success

Using a Numeric User ID

Numeric IDs help orchestration tools verify that a container does not run as root.

USER 10001:10001

Choose an ID above 10000 to avoid clashes with real accounts on the host.

Setting the User at Runtime

The --user flag overrides the image setting for one container.

docker run --user 10001:10001 my-app

Ports Below 1024

Linux restricts ports below 1024 to privileged accounts. A non-root web server cannot listen on port 80 in the usual way. Configure the application to listen on a high port such as 8080 and map it outward.

Inside container : application listens on 8080
Outside          : docker run -p 80:8080 my-app

Many Official Images Help You

Some images ship with a ready-made unprivileged account. The nginx unprivileged image and the node image with its built-in node user are examples.

FROM node:20-alpine
USER node

Testing Your Setup

docker build -t safe-app .
docker run --rm safe-app whoami
appuser

A result other than root confirms the change.

Common Mistakes

  • Placing USER too early, which blocks later RUN instructions that need to install packages. Install first and switch the user near the end.
  • Forgetting ownership of mounted volumes. Volume folders on the host keep the host owner, so match user IDs or fix permissions.
  • Believing a non-root user makes the container completely safe. It reduces risk and works best together with other habits from this category.

Real-World Scenario

A small business hosts a photo upload site in a container that runs as root. The upload feature has a flaw that lets a visitor write a file into any folder. Because the application runs as root, the visitor can overwrite system files inside the container and plant a harmful script. With a non-root user, the same flaw can write only into the folders that the user owns, and the system files stay protected.

The fix takes five lines in the Dockerfile and one extra check during testing. The cost is tiny compared with the damage a root compromise causes.

Volumes and User IDs

A bind mount maps a host folder into the container. Linux compares numeric user IDs, not names. When the host folder belongs to user 1000 and the container runs as user 10001, the container cannot write to the folder. Pick the container user ID to match the host owner, or change the folder ownership with chown. Named volumes avoid most of this trouble because Docker creates them with open enough permissions for the first container to use.