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.
