Run Docker nginx as Non-Root-User
Tags: docker, devops
Published: 2020-01-06 21:43:52

---
title: Run Docker nginx as Non-Root-User
published: true
date: 2020-01-06 00:00:00 UTC
tags: docker, devops
canonical_url: https://www.rockyourcode.com/run-docker-nginx-as-non-root-user/
---


> How to run nginx as non-privileged user with Docker

**nginx** is an open-source solution for web serving and reverse proxying your web application. You put it "in front" of your different services, and nginx can route the traffic to the correct url.  
That's useful for micro-services, for example.

Per default, **nginx runs as root user**. Why?  
Only root processes can listen to ports below 1024. The default port for web applications is usually 80 or 443.

But that shouldn't be a detriment to running Docker as a non-privileged user. After all, we can **[forward ports][forwardport]**.

That's the `-p 80:8080` syntax that you might have seen in a `docker run` command.

You [map the TCP port 8080 from the Docker container to port 80 on the Docker host](https://docs.docker.com/config/containers/container-networking/) (for example, your nginx webserver that listens to port 80).

For **security reasons**, it's better to **[run a Docker container as a non-root user][bitnami]**.

So, how can we achieve that?

# Pull The Default Docker Image for nginx

Let's say we have a React application and a backend written with Python and Flask.  
During development, we might have used the [official Docker node image](https://hub.docker.com/_/node) and the [official Docker python image](https://hub.docker.com/_/python).

Now, for deployment with nginx, we'll use the **[official nginx Docker image][dockernginx]**.

I prefer the slimmed-down **Debian** distribution instead of the Alpine images that most people seem to use.  
Most of the time, Alpine doesn't contain essential tools and packages, and you'll have to painstakingly debug why your installation doesn't work.  
The slim version of Debian (currently "Buster Slim") ships with better defaults, and is only slightly larger.

For a better understanding, I recommend the article [The best Docker base image for your Python application][pythonspeed].

Your `Dockerfile` will start like this:

```docker
FROM nginx:1.17.6
```

The good news is that the official Docker build for nginx already **installs a non-root user called `nginx`**.

The bad news is that the `nginx` user **doesn't have all the permissions it needs to run your program**.

# Adjust nginx Configuration

You'll need to replace the standard nginx configuration: the `/etc/nginx/nginx.conf` file and the `/etc/nginx/conf.d/default.conf`.

For the `nginx.conf` file, you should check how the default config looks like, and delete the `user` directive (first line of the file).

Here's the `/etc/ngnix/nginx.conf` that works with Debian:

```nginx
# user user; # <- delete this line
worker_processes  1;
error_log  /var/nginx/log/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
```

Create your nginx configuration file, `etc/nginx/conf.d/default.conf`.

Example:

```nginx
server {
  listen $PORT;

  root /usr/share/nginx/html;
  index index.html index.html;

  location / {
    try_files $uri /index.html =404;
  }

  location /auth {
    proxy_pass          http://127.0.0.1:5000;
    proxy_http_version  1.1;
    proxy_redirect      default;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
    proxy_set_header    Host $host;
    proxy_set_header    X-Real-IP $remote_addr;
    proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Host $server_name;
  }

  location /users {
    proxy_pass          http://127.0.0.1:5000;
    proxy_http_version  1.1;
    proxy_redirect      default;
    proxy_set_header    Upgrade $http_upgrade;
    proxy_set_header    Connection "upgrade";
    proxy_set_header    Host $host;
    proxy_set_header    X-Real-IP $remote_addr;
    proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header    X-Forwarded-Host $server_name;
  }
}
```
You can see the files on [GitHub](https://github.com/sophiabrandt/flask-react-auth/tree/master/services/nginx) if you like.

The Python flask application runs on port 5000 inside the Docker container and has two routes: `/auth` and `/users`.  
Only the front-end client will query these routes. Your users won't access them.  
nginx will serve the React application from the root route (`/`) to the public.

Now, copy those two customized configurations into your Docker container.  
For the Flask application, we'll also need to install Python.

```docker
FROM nginx:1.17.6

# install python3
RUN apt-get update && \
    apt-get install -y --no-install-recommends python3

COPY ./nginx.conf /etc/nginx/nginx.conf
COPY ./default.conf /etc/nginx/conf.d/default.conf
```

# Add User Permissions

We need to give the `nginx` user permissions to several files.

Our working directory on the docker container will be `/app`. We'll copy the source code from our local machine into that folder later.

The `nginx` user needs permission for the `WORKDIR` and also for `/var/cache/nginx` (cache), `/etc/nginx/conf.d` (for the nginx configuration), and the `tmp` folder (for `pid` and logging).

We have to create some of those files within the `Dockerfile`, otherwise, the container won't run.

```docker
FROM nginx:1.17.6

RUN apt-get update && \
    apt-get install -y --no-install-recommends python3

COPY ./nginx.conf /etc/nginx/nginx.conf
COPY ./default.conf /etc/nginx/conf.d/default.conf

WORKDIR /app

# add permissions for nginx user
RUN chown -R nginx:nginx /app && chmod -R 755 /app && \
        chown -R nginx:nginx /var/cache/nginx && \
        chown -R nginx:nginx /var/log/nginx && \
        chown -R nginx:nginx /etc/nginx/conf.d
RUN touch /var/run/nginx.pid && \
        chown -R nginx:nginx /var/run/nginx.pid

USER nginx
```

That's the bulk of our work.

Now we can copy our local code into the container, set environment variables, and run the start command.

Example:

```docker
FROM nginx:1.17.6

RUN apt-get update && \
    apt-get install -y --no-install-recommends python3

COPY ./nginx.conf /etc/nginx/nginx.conf
COPY ./default.conf /etc/nginx/conf.d/default.conf

WORKDIR /app

# add permissions
RUN chown -R nginx:nginx /app && chmod -R 755 /app && \
        chown -R nginx:nginx /var/cache/nginx && \
        chown -R nginx:nginx /var/log/nginx && \
        chown -R nginx:nginx /etc/nginx/conf.d
RUN touch /var/run/nginx.pid && \
        chown -R nginx:nginx /var/run/nginx.pid

# switch to non-root user
USER nginx

# add Python app
COPY . .

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV FLASK_ENV production
ENV SECRET_KEY $SECRET_KEY

# run server
CMD gunicorn -b 0.0.0.0:5000 manage:app --daemon && \
      sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/conf.d/default.conf && \
      nginx -g 'daemon off;'
```

When you run the container, don't forget to use ports higher than 1024.

Example:

```sh
docker run -d -p 8007:5000 my-nginx-app
```

If you want to take a look at a working multi-stage docker build, you can check my [deploy Dockerfile (for Heroku)](https://github.com/sophiabrandt/flask-react-auth/blob/master/Dockerfile-deploy) for the [Flask React Auth course by Testdriven.io][testdriven].

# Recap

Deploying nginx with Docker as non-root-user _is_ possible, and improves the security of your Docker containers.

You have to jump through some hoops to set the correct permissions for the user, but then it works like a charm.

### Further Reading

- [Nginx in Docker without Root][pjdietz] by PJ Dietz
- [Running Nginx as non root user][so] on StackOverflow
- [Nginx in Docker as non root user][jozef] by Jozef Bilka
- [Why non-root containers are important for security][bitnami] on Bitnami
- [The best Docker base image for your Python application][pythonspeed] by Itamar Turner-Tauring
- [Official Docker image for nginx][dockernginx]
- [Authentication with Flask, React, and Docker][testdriven] by Michael Herman

[so]: https://stackoverflow.com/questions/42329261/running-nginx-as-non-root-user
[jozef]: https://blog.bilka.sk/nginx-in-docker-as-non-root-user/
[pjdietz]: http://pjdietz.com/2016/08/28/nginx-in-docker-without-root.html
[forwardport]: https://runnable.com/docker/binding-docker-ports
[bitnami]: https://engineering.bitnami.com/articles/why-non-root-containers-are-important-for-security.html
[dockernginx]: https://hub.docker.com/_/nginx
[pythonspeed]: https://pythonspeed.com/articles/base-image-python-docker-images/
[testdriven]: https://testdriven.io/courses/auth-flask-react