Why I Stopped Running My Docker Containers as Root (And Why You Should Too)

A few months ago, I spun up a new project and, as always, reached for a trusty Docker image from Docker Hub. “nginx:latest” it was. Within seconds, my app was grinning at me from localhost - the beauty of containers! Then, a ping from my security-conscious friend derailed my developer joy:
“Hey, are you running that container as root?”
Cue the nervous laughter. Turns out: most popular Docker images run as root by default. Super convenient, until you realize they’re a security disaster waiting to happen. One clever exploit, and suddenly your containerized dream is trench-coating around your host filesystem.
So, here’s how I learned to set up my containers - specifically NGINX- to run as a non-root user. You can too, with just a little extra work!
The Problem with Root
Running as root inside your container is like leaving the keys in your car parked on a busy street. Anyone who gets in has all the power—not just inside the container, but sometimes on the host too. That’s why security best practices in 2025 and beyond strongly recommend running everything with the least privileges possible, especially in production.
Setting Up a “Non-Root” NGINX Container
Here’s the directory structure we’ll use:
text.
|-conf
| |-nginx.conf
| |-conf.d
| |-default.conf
|-Dockerfile
1. Create the Dockerfile
First, let’s create a new Dockerfile that does a few important things:
Adds a non-root user (app with id 5000).
Ensures writable directories belong to our new user.
Switches the container to run as that user.
FROM nginx:latest
RUN useradd -u 5000 app && \
mkdir -p /var/run/nginx /var/tmp/nginx && \
chown -R app:app /usr/share/nginx /var/run/nginx /var/tmp/nginx
COPY conf/nginx.conf /etc/nginx/nginx.conf
COPY conf/conf.d/default.conf /etc/nginx/conf.d/
USER app:app
2. Configure NGINX for the User
conf/nginx.conf
:
# user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx/nginx.pid;
events {
worker_connections 1024;
}
http {
client_body_temp_path /var/tmp/nginx/client_body;
fastcgi_temp_path /var/tmp/nginx/fastcgi_temp;
proxy_temp_path /var/tmp/nginx/proxy_temp;
scgi_temp_path /var/tmp/nginx/scgi_temp;
uwsgi_temp_path /var/tmp/nginx/uwsgi_temp;
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main ' - [] "" ' ' "" ' '"" "" ';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
conf/conf.d/default.conf
:
server {
listen 443;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
3. Build and Run Your Image
Once everything’s in place, go to your Dockerfile’s directory and build:
docker build -t my-nginx-nonroot .
Run as usual:
docker run -d --name mynginx -p 443:443 my-nginx-nonroot
4. Confirm it’s Working!
You can double-check that your container isn’t running as root:
$ docker exec <containerid> whoami
app
$ docker exec <containerid> id
uid=5000(app) gid=5000(app) groups=5000(app)
Why Bother?
This might feel like extra work - especially when things “just work” if you leave everything as root. But following this process dramatically reduces your attack surface, lowering the chance that one container exploit turns into a full-blown crisis on your server.
Now, every time someone asks if I’m running as root, I can confidently say, “Not anymore!” (And neither should you.)
Keep shipping, securely!
This article was inspired by and adapted from the fantastic walkthrough at play-with-docker.com. Check it out for a deeper dive and more examples!
Subscribe to my newsletter
Read articles from Muskan Agrawal directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Muskan Agrawal
Muskan Agrawal
Cloud and DevOps professional with a passion for automation, containers, and cloud-native practices—committed to sharing lessons from the trenches while always seeking new challenges. Combining hands-on expertise with an open mind, I write to demystify the complexities of DevOps and grow alongside the tech community.