Docker Non-Root
The practice of configuring Docker containers to run as a non-root user (UID > 0) to limit the damage a compromised container process can do to the host system and other containers.
Docker Non-Root — Running Containers Without Root Privileges
What Is Docker Non-Root in Simple Terms?
By default, every Docker container runs as root (UID 0). This means if an attacker exploits a vulnerability in your application and gets code execution inside the container, they have root privileges inside the container. While container isolation limits what they can do, running as root dramatically increases the potential damage.
Running as a non-root user is one of the most impactful security improvements you can make to any Docker container.
Root container exploited: Attacker has UID 0 inside container Can read all files in the container Can modify /etc/passwd, install tools Higher chance of privilege escalation to host All setuid binaries executable Non-root container exploited: Attacker has UID 1001 inside container Cannot read root-owned files Cannot install system packages Cannot execute setuid binaries Much harder to escalate privilegesAdding Non-Root User in Dockerfile
# Method 1 — Create a new user (most explicit)FROM node:20-alpineWORKDIR /app # Create group and userRUN addgroup -S appgroup && \ adduser -S appuser -G appgroup COPY package.json package-lock.json ./RUN npm ci --omit=devCOPY dist/ ./dist/ # Give ownership to non-root userRUN chown -R appuser:appgroup /app # Switch to non-root before CMDUSER appuser EXPOSE 8080CMD ["node", "dist/server.js"]# Method 2 — Use the pre-existing node user (node:20 images include it)FROM node:20-alpineWORKDIR /app # Copy with correct ownership from the startCOPY --chown=node:node package.json package-lock.json ./RUN npm ci --omit=devCOPY --chown=node:node dist/ ./dist/ # Switch to node user (UID 1000, already exists in node images)USER node CMD ["node", "dist/server.js"]Verifying Non-Root
# Build and verifydocker build -t myapp:latest .docker run --rm myapp:latest whoami# appuser <- not root docker run --rm myapp:latest id# uid=1001(appuser) gid=1001(appgroup) # What user is a running container using?docker inspect payment-api \ --format '{{.Config.User}}'# appuser # Check existing containers for rootdocker ps -q | xargs docker inspect \ --format '{{.Name}}: {{.Config.User}}' \ | grep -v "appuser\|node\|1000\|1001"# Any empty result = running as rootFile Permission Issues
# Common issue: non-root user cannot write to a directory# Solution: set correct ownership before switching user FROM node:20-alpineWORKDIR /app COPY --chown=node:node . .RUN npm ci --omit=dev # Create directories the app needs to write toRUN mkdir -p /app/logs /app/uploads && \ chown -R node:node /app/logs /app/uploads USER nodeCMD ["node", "server.js"]RememberSet the USER instruction in your Dockerfile before CMD or ENTRYPOINT. The process that starts the container runs as this user. If you set USER after CMD, the CMD runs as the user that was active before USER — which is root.
SecurityEven with a non-root USER, if you mount the Docker socket (
-v /var/run/docker.sock:/var/run/docker.sock) into the container, any user inside that container can control Docker on the host and effectively has root access. Non-root inside the container provides real protection only when combined with not mounting sensitive host resources.
Frequently Asked Questions
Why does it matter if a container process runs as root when the container is already isolated by namespaces?
Namespace isolation is not a hard security boundary — container escape vulnerabilities (kernel bugs, misconfigured capabilities, Docker socket exposure) have been found repeatedly over the years, and when one is exploited, a process running as UID 0 inside the container often maps to root-equivalent privileges on the host or at least far more capability than a non-root user would have. Running as non-root is defense in depth: it doesn't stop every attack, but it meaningfully narrows what a successful one can do.
What's the common mistake teams make when trying to add USER to an existing Dockerfile?
Adding `USER 1000` without first ensuring that user has write access to directories the app needs at runtime (log paths, temp directories, mounted volumes) — the container then crashes on startup with permission errors that look unrelated to the USER change. The fix is setting ownership explicitly with `chown` during the build (while still root, before the USER instruction switches identity) rather than trying to fix permissions after the fact at runtime.