Nginx in Docker as non root user

Nginx in Docker as non root user

2019, Oct 01    

By default docker containers run as root. I don`t like that.

What you need to know

If you want to run nginx as non root user, you have to meet these requirements:

  • non-root-user needs read access to web app files
  • non-root-user needs read/write access to /var/run/nginx.pid or any other pid file of nginx (pid file can be changed is nginx.conf)
  • non-root-user needs read/write access to /var/cache/nginx
  • free port(s) to above 1024. Only root can listen on ports below 1024, so you will need to use higher-numbered ports (not HTTP/80 and HTTPS/443). This is not an issue since you can map your host port to your container port

Prepare user on docker host system

First, you need to create user and group on docker host:

#create Group_Name with gid Group_ID

sudo groupadd -g Group_ID Group_Name

#create User_name with gid User_ID 

sudo adduser -u User_ID User_name 

#add User_name to Group_Name

sudo usermod -a -G Group_Name User_name

So in our case:

sudo groupadd -g 1443 non-root-user-group
sudo adduser -u 1443 non-root-user
sudo usermod -a -G non-root-user-group non-root-user

Prepare config files on docker host system

Nginx needs at least nginx.conf and default.conf to be able to start. IMHO best way is to start with the config files provided in the offical docker image. An easy way to copy the original files from the image to your host is to start a container and use docker cp:

docker run --name nginx -d nginx:stable
docker cp nginx:/etc/nginx/ /dockervolumes/nginx-unprivileged/
docker stop nginx
docker rm nginx

From file nginx.conf remove line

user nginx;

And change ports in default.conf (and in all vhost.conf files) from 80 to your choice of free port above 1024, for example 8080 and from 443 to 8443. So your default.conf may look like this:

server {
    listen 8080;
    server_name localhost;
    location / {
        root /var/www;
        index index.html index.htm;
    }
}

Dockerfile

Here is an example of Dockerfile from official nginx image with modified user and rights.

ARG IMAGE=alpine:3.10
FROM \$IMAGE

LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"

ENV NGINX_VERSION 1.17.3
ENV NJS_VERSION 0.3.5
ENV PKG_RELEASE 1

RUN set -x \


# create nginx user/group first, to be consistent throughout docker variants


    && addgroup -g 1443 -S nginx \

    && adduser -S -D -H -u 1443 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx \

    && apkArch="$(cat /etc/apk/arch)" \

    && nginxPackages=" \

        nginx=${NGINX_VERSION}-r${PKG_RELEASE} \

        nginx-module-xslt=${NGINX_VERSION}-r${PKG_RELEASE} \

        nginx-module-geoip=${NGINX_VERSION}-r${PKG_RELEASE} \

        nginx-module-image-filter=${NGINX_VERSION}-r${PKG_RELEASE} \

        nginx-module-njs=${NGINX_VERSION}.${NJS_VERSION}-r${PKG_RELEASE} \

    " \

    && case "$apkArch" in \

        x86_64) \


# arches officially built by upstream


            set -x \

            && KEY_SHA512="e7fa8303923d9b95db37a77ad46c68fd4755ff935d0a534d26eba83de193c76166c68bfe7f65471bf8881004ef4aa6df3e34689c305662750c0172fca5d8552a *stdin" \

            && apk add --no-cache --virtual .cert-deps \

                openssl \

            && wget -O /tmp/nginx_signing.rsa.pub https://nginx.org/keys/nginx_signing.rsa.pub \

            && if [ "$(openssl rsa -pubin -in /tmp/nginx_signing.rsa.pub -text -noout | openssl sha512 -r)" = "$KEY_SHA512" ]; then \

                echo "key verification succeeded!"; \

                mv /tmp/nginx_signing.rsa.pub /etc/apk/keys/; \

            else \

                echo "key verification failed!"; \

                exit 1; \

            fi \

            && printf "%s%s%s\n" \

                "https://nginx.org/packages/mainline/alpine/v" \

                `egrep -o '^[0-9]+\.[0-9]+' /etc/alpine-release` \

                "/main" \

            | tee -a /etc/apk/repositories \

            && apk del .cert-deps \

            ;; \

        *) \


# we're on an architecture upstream doesn't officially build for


# let's build binaries from the published packaging sources


            set -x \

            && tempDir="$(mktemp -d)" \

            && chown nobody:nobody $tempDir \

            && apk add --no-cache --virtual .build-deps \

                gcc \

                libc-dev \

                make \

                openssl-dev \

                pcre-dev \

                zlib-dev \

                linux-headers \

                libxslt-dev \

                gd-dev \

                geoip-dev \

                perl-dev \

                libedit-dev \

                mercurial \

                bash \

                alpine-sdk \

                findutils \

            && su nobody -s /bin/sh -c " \

                export HOME=${tempDir} \

                && cd ${tempDir} \

                && hg clone https://hg.nginx.org/pkg-oss \

                && cd pkg-oss \

                && hg up ${NGINX_VERSION}-${PKG_RELEASE} \

                && cd alpine \

                && make all \

                && apk index -o ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz ${tempDir}/packages/alpine/${apkArch}/*.apk \

                && abuild-sign -k ${tempDir}/.abuild/abuild-key.rsa ${tempDir}/packages/alpine/${apkArch}/APKINDEX.tar.gz \

                " \

            && echo "${tempDir}/packages/alpine/" >> /etc/apk/repositories \

            && cp ${tempDir}/.abuild/abuild-key.rsa.pub /etc/apk/keys/ \

            && apk del .build-deps \

            ;; \

    esac \

    && apk add --no-cache $nginxPackages \


# if we have leftovers from building, let's purge them (including extra, unnecessary build deps)


    && if [ -n "$tempDir" ]; then rm -rf "$tempDir"; fi \

    && if [ -n "/etc/apk/keys/abuild-key.rsa.pub" ]; then rm -f /etc/apk/keys/abuild-key.rsa.pub; fi \

    && if [ -n "/etc/apk/keys/nginx_signing.rsa.pub" ]; then rm -f /etc/apk/keys/nginx_signing.rsa.pub; fi \


# remove the last line with the packages repos in the repositories file


    && sed -i '$ d' /etc/apk/repositories \


# Bring in gettext so we can get `envsubst`, then throw


# the rest away. To do this, we need to install `gettext`


# then move `envsubst` out of the way so `gettext` can


# be deleted completely, then move `envsubst` back.


    && apk add --no-cache --virtual .gettext gettext \

    && mv /usr/bin/envsubst /tmp/ \

    \

    && runDeps="$( \

        scanelf --needed --nobanner /tmp/envsubst \

            | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \

            | sort -u \

            | xargs -r apk info --installed \

            | sort -u \

    )" \

    && apk add --no-cache $runDeps \

    && apk del .gettext \

    && mv /tmp/envsubst /usr/local/bin/ \


# Bring in tzdata so users could set the timezones through the environment


# variables


    && apk add --no-cache tzdata

# implement changes required to run NGINX as an unprivileged user


RUN sed -i -e '/listen/!b' -e '/80;/!b' -e 's/80;/8080;/' /etc/nginx/conf.d/default.conf \

 && sed -i -e '/user/!b' -e '/nginx/!b' -e '/nginx/d' /etc/nginx/nginx.conf \

 && sed -i 's!/var/run/nginx.pid!/tmp/nginx.pid!g' /etc/nginx/nginx.conf \

 && sed -i "/^http {/a \ proxy_temp_path /tmp/proxy_temp;\n client_body_temp_path /tmp/client_temp;\n fastcgi_temp_path /tmp/fastcgi_temp;\n uwsgi_temp_path /tmp/uwsgi_temp;\n scgi_temp_path /tmp/scgi_temp;\n" /etc/nginx/nginx.conf

RUN chown -R 1443:0 /var/cache/nginx \

 && chmod -R g+w /var/cache/nginx \

 && touch /var/run/nginx.pid \

 && chown -R 1443:0 /var/run/nginx.pid \

 && ln -sf /dev/stdout /var/log/nginx/access.log \

 && ln -sf /dev/stderr /var/log/nginx/error.log

EXPOSE 8080 8443

STOPSIGNAL SIGTERM

USER 1443

CMD ["nginx", "-g", "daemon off;"]

docker-compose.yaml

version: "3.2"



services:

  nginx-unprivileged:

    build:

      context: ./nginx-unprivileged

      dockerfile: Dockerfile

    image: yoyanec/nginx-unprivileged:1.17.3

    container_name: nginx-unprivileged

    volumes: 

      - /dockervolumes/nginx-unprivileged/nginx:/etc/nginx:ro 

      - /dockervolumes/nginx-unprivileged/www:/var/www:ro

    ports: 

      - "80:8080" 

      - "443:8443"

    networks: 

      - nginx-unprivileged-network

    restart: unless-stopped

    environment: 

      - TZ=Europe/Bratislava

How to start unprivileged nginx service

That`s all. Only thing you need to do to start unprivileged nginx docker service is:

sudo docker-compose up -d nginx-unprivileged