🔒 Root in Container = Root on Host (Almost)
Docker defaults to root user. Container escape = full host compromise. Run as non-root for defense in depth.
📝 Dockerfile Best Practice
FROM node:18-alpine
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
# Set ownership of app directory
WORKDIR /app
COPY --chown=nodejs:nodejs . .
# Switch to non-root user
USER nodejs
# Run as non-root
CMD ["node", "app.js"]
# Verify inside container
# docker run myapp whoami
# Output: nodejs (not root!)
🎯 Run-time User Override
# Override user at run time
docker run --user 1001:1001 myapp
# Run as non-root with specific group
docker run --user nodejs:nodejs myapp
# In docker-compose
services:
app:
image: myapp
user: "1001:1001"
# Read-only root filesystem
docker run --read-only myapp
# Drop all capabilities
docker run --cap-drop=ALL --cap-add=NET_ADMIN myapp
✅ Security Checklist
- Use specific user ID (not generic ‘node’)
- Never run as root (except base images that need root for installs)
- Switch user at the END (so installs run as root, app runs as user)
- Use –cap-drop=ALL, add only needed capabilities
- Set read-only root filesystem when possible
“Security audit found all our containers running as root. Added USER nodejs to Dockerfiles. Now container compromise ≠ host compromise. Simple change, big security win.”
