Back to Blog
Lesson 31 of the Docker: Containers & Your First Image course
DevOpsAugust 18, 20264 min read

Understanding User Permissions: A Non-Root Security Guide

Stop running containers as root. Learn to create system users, manage file permissions, and apply best practices for secure non-root Docker deployments.

dockersecuritydevopslinuxcontainersbest-practices
A hand holding a smartphone displaying a VPN app screen for secure online browsing.

Previously in this course, we explored advanced Dockerfile directives to improve container robustness. Now, we shift our focus to security by moving away from the default root user.

By default, Docker containers run processes as the root user. While convenient for development, it’s a significant security risk; if an attacker compromises your application, they gain full administrative control over the container, making it much easier to break out to the host system. Implementing non-root execution is one of the most effective best practices you can adopt.

Why Avoid Root?

In Linux, the root user has unrestricted access to the entire system. When a container runs as root, any file created by your application is owned by root on the host machine (if using volumes), leading to permission headaches. More importantly, it violates the principle of least privilege.

Creating and Switching Users

To run a container as a non-privileged user, we must define that user in our Dockerfile. We use the RUN directive to create a user and the USER directive to switch the active context.

Consider this Dockerfile example for a simple Node.js application:

Dockerfile
FROM node:18-slim

# Create a group and a user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Set the working directory
WORKDIR /app

# Change ownership of the directory to the new user
RUN chown -R appuser:appuser /app

# Switch to the non-root user
USER appuser

# Copy app files
COPY . .

CMD ["node", "app.js"]

Breakdown of the Directives:

  1. groupadd -r / useradd -r: We create a "system" user and group. The -r flag creates a system account (usually with a lower UID/GID), which is standard for services.
  2. chown: This is crucial. If you don't change the ownership of your application directory, your new user won't be able to write logs or temporary files, causing the app to crash.
  3. USER appuser: Every command following this line (including CMD and ENTRYPOINT) will execute as appuser instead of root.

Managing File Permissions

When you mount volumes, you often run into "Permission Denied" errors. This happens because the user ID (UID) inside the container must match the UID on your host machine for seamless access.

If you are struggling with these conflicts, fixing Docker user permissions is a common hurdle. A quick tip: always check your UID by running id on your host terminal and ensure your Dockerfile creates the user with that specific ID if necessary.

Hands-on Exercise

Modify your current project's Dockerfile to drop root privileges:

  1. Add the useradd command shown above to your Dockerfile.
  2. Use chown to ensure your application directory is owned by your new user.
  3. Add the USER directive.
  4. Build the image and run it. Verify the user by executing docker run --rm <image-name> whoami. It should return appuser, not root.

Common Pitfalls

  • Assuming Root is Needed: Many developers think they need root to bind to ports below 1024. While true for standard Linux systems, Docker’s port mapping allows a non-root process to bind to these ports because the mapping happens at the host level.
  • Ignoring Cleanup: If you run apt-get install as root to add tools before switching users, ensure you clear the cache in the same RUN command to keep your layers small, as discussed in our guide on optimizing image size.
  • Hardcoding UIDs: Avoid hardcoding UIDs if possible, as they might conflict with existing users on different host environments.

FAQ

Q: Can I switch back to root later? A: Yes, you can use USER root in the Dockerfile again, but you should rarely need to do this.

Q: What if my app crashes because it can't write to /tmp? A: You may need to chown specific directories like /tmp or create a subdirectory owned by your user and point your application's temp path there.

Q: Do I need to worry about this if I'm just developing locally? A: While it's optional for local-only apps, practicing this now builds the muscle memory for production-ready security, which is vital for hardening your DevOps pipelines.

Recap

Running as a non-root user is a foundational security step. By creating a dedicated system user, setting correct file permissions with chown, and switching contexts with USER, you significantly reduce the blast radius of potential container vulnerabilities. These best practices ensure your infrastructure remains resilient and manageable.

Up next: We will explore how to further customize base images to create standardized environments for your team.

Similar Posts