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.
TEXT
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
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"]
Dockerfile
# 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
Bash
# 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
Dockerfile
# 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"]REMEMBER THIS**Remember:** Set 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.
COMMON MISTAKE / WARNING**Security:** Even 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.