Back to Blog
DevOpsJuly 11, 20264 min read

Fixing Docker User Permissions: A Non-Root Security Guide

Learn how to implement docker user permissions correctly. Stop running containers as root and fix those annoying "docker volume permission denied" errors.

dockerdevopssecuritylinuxcontainers

Running containers as root is the default, but it's a security liability I try to avoid whenever possible. If your container gets compromised, the attacker starts with root access to the container's filesystem. While that’s not always full host access, it’s a massive head start for any exploit.

I’ve spent plenty of time fighting "permission denied" errors when trying to switch to a non-root user. You define a user, run the container, and suddenly your app can't write to the mounted volume because the host UID doesn't match the container UID.

Why the "Permission Denied" Error Happens

When you mount a volume from your host machine into a container, the kernel maps the file ownership based on numeric UIDs (User IDs), not usernames.

If your host user has a UID of 1000 and your Dockerfile creates a user with UID 1001, the containerized process won't have write access to files created by your host user. This is a classic mismatch that creates a "docker volume permission denied" scenario. It’s frustrating, but it’s actually a sign that your security boundaries are working.

Implementing Non-Root Security Best Practices

The most robust way to handle this is to align your container's UID with your host's UID during the build or at runtime. Here is the strategy I use for most of my projects.

  1. Create a specific user in your Dockerfile.
  2. Ensure the application directory is owned by that user.
  3. Use build arguments to allow flexibility.

Here is what a clean Dockerfile looks like:

Dockerfile
FROM node:20-slim

# Create a non-root user
ARG USER_ID=1000
ARG GROUP_ID=1000

RUN groupadd -g ${GROUP_ID} appgroup && \
    useradd -u ${USER_ID} -g appgroup -m appuser

WORKDIR /app
RUN chown -R appuser:appgroup /app

# Switch to the non-root user
USER appuser

COPY . .
CMD ["node", "index.js"]

Dealing with Volume Mounts

Even with the setup above, if you mount a local folder into /app, the volume mount might override the ownership. If you're still seeing permission issues, you have two options:

  • Option A: The chown approach. Run a quick command to fix permissions on the host side. This is easy but feels manual.
  • Option B: The Entrypoint script. Use a small shell script that runs as root during container startup, updates the UID of the existing user to match the mounted volume's owner, and then executes the application.

If you're interested in hardening your infrastructure further, check out my notes on Docker Security: Implementing Immutable Infrastructure via Read-Only Root. It’s a great next step after you’ve successfully migrated to non-root users.

Comparison: Managing Permissions

MethodSecurity LevelComplexityBest For
Root UserLowZeroQuick local testing
Fixed UID (1000)MediumLowStandardized CI/CD
Dynamic EntrypointHighMediumShared dev environments

A Quick Warning on Security

Dropping root is great, but it doesn't solve everything. You should also be looking at Preventing Arbitrary File Write Vulnerabilities in Node.js and PHP to ensure your application code isn't doing the heavy lifting for an attacker, even if your user permissions are locked down.

FAQ

Why shouldn't I just use chmod 777 on my volume? Please don't. That grants read, write, and execute permissions to everyone on the system. It’s a massive security hole that makes your file system vulnerable to any process, regardless of user.

Does running as non-root affect network performance? Not at all. The kernel handles the network stack regardless of which user is running the process. The only restriction is that non-root users cannot bind to privileged ports (anything below 1024). You'll need to map your application to a higher port like 3000 or 8080.

Is it always necessary to match UIDs? If you aren't using volume mounts for persistent data, you don't need to match the host UID. However, for development workflows where you mount code or data, matching them saves you about 90% of the debugging headaches related to file access.

If you find yourself needing help architecting these setups for production, I offer CI/CD Pipeline & Docker Containerization services to help get your infrastructure to a "boring" and stable state.

I’m still experimenting with rootless Docker modes for more complex setups, but for 99% of web apps, the Dockerfile-based user management above is the sweet spot. It’s not perfect, but it’s a hell of a lot safer than running as root.

Similar Posts