Running Nginx official image as non root

Hello everyone,

I search for a way to start nginx docker container a non root user. Here’s my Dockerfile:

My concern is that every time i run my image, container still running with root user.

Is there a way to achieve this using nginx:latest image ?

Thank you,

hello @p3dr07 ,

I have used a below sample docker file

FROM nginx
USER nginx
CMD ["nginx","-g","daemon off"]

and below are the test result where I can see its running as non-root nginx user.

docker run --rm -it 94b26ba1bc4a bash 
nginx@91b96a15d72d:/$ whoami 
nginx
nginx@91b96a15d72d:/$ id
uid=101(nginx) gid=101(nginx) groups=101(nginx)
nginx@91b96a15d72d:/$ apk add curl
bash: apk: command not found
nginx@91b96a15d72d:/$ apt update  
Reading package lists... Done
E: List directory /var/lib/apt/lists/partial is missing. - Acquire (13: Permission denied)
nginx@91b96a15d72d:/$

have tried rebuilding the image by clearing the cache?

could you please share exec output where exactly you are checking it’s running as the root user?

Hi @ alishah730

Thanks for your reply.

In fact container is executed as nginx user. The issue is when user try to access /etc/nginx/ssl.
Folder permissions and user are not set as i mentionned in my dockerfile.

I change Dockerfile but it dosen’t work anymore: See below

RUN mkdir -p /etc/nginx/ssl/ && \
    chown -R nginx:nginx /etc/nginx/ssl/ && \
    chmod -R 755 /etc/nginx/ssl/

Folder permission inside container :slight_smile:

ssl_fodler_perm

directory still belongs to root user.

Any idea ?

Thank you,

hi @p3dr07
unable to reproduce the issue.

my docker file

FROM nginx
RUN mkdir -p /etc/nginx/ssl/ && \
    chown -R nginx:nginx /etc/nginx/ssl/ && \
    chmod -R 755 /etc/nginx/ssl/
USER nginx
CMD ["nginx","-g","daemon off"]

output

nginx@0d476d064908:/$ cd /etc/nginx/
nginx@0d476d064908:/etc/nginx$ ls -l
total 32
drwxr-xr-x 2 root  root  4096 Apr 12 04:42 conf.d
-rw-r--r-- 1 root  root  1007 Mar 28 15:01 fastcgi_params
-rw-r--r-- 1 root  root  5349 Mar 28 15:01 mime.types
lrwxrwxrwx 1 root  root    22 Mar 28 16:49 modules -> /usr/lib/nginx/modules
-rw-r--r-- 1 root  root   648 Mar 28 16:49 nginx.conf
-rw-r--r-- 1 root  root   636 Mar 28 15:01 scgi_params
drwxr-xr-x 2 nginx nginx 4096 Apr 25 13:30 ssl
-rw-r--r-- 1 root  root   664 Mar 28 15:01 uwsgi_params
nginx@0d476d064908:/etc/nginx$

could you please share your output from the below command?

docker info
docker version

Docker info

Client:
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.10.4
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v2.17.2
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 10
  Running: 9
  Paused: 0
  Stopped: 1
 Images: 10
 Server Version: 23.0.4
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Using metacopy: false
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 2806fc1057397dbaeefbea0e4e17bddfbd388f38
 runc version: v1.1.5-0-gf19387a
 init version: de40ad0
 Security Options:
  seccomp
   Profile: builtin
  selinux
  userns
  cgroupns
 Kernel Version: 5.10.0-21-cloud-amd64
 Operating System: Debian GNU/Linux 11 (bullseye)
 OSType: linux
 Architecture: x86_64
 CPUs: 2
 Total Memory: 7.588GiB
 Name: vps-4831892b
 ID: 93f87603-4416-46a7-a48d-edced321618b
 Docker Root Dir: /var/lib/docker/165536.165536
 Debug Mode: true
  File Descriptors: 108
  Goroutines: 100
  System Time: 2023-04-25T13:46:09.395656533Z
  EventsListeners: 1
 Username: p3dr07
 Registry: https://index.docker.io/v1/
 Experimental: false
 Insecure Registries:
  127.0.0.0/8
 Live Restore Enabled: true

Docker version

Client: Docker Engine - Community
 Version:           23.0.4
 API version:       1.42
 Go version:        go1.19.8
 Git commit:        f480fb1
 Built:             Fri Apr 14 10:32:17 2023
 OS/Arch:           linux/amd64
 Context:           default

Server: Docker Engine - Community
 Engine:
  Version:          23.0.4
  API version:      1.42 (minimum version 1.12)
  Go version:       go1.19.8
  Git commit:       cbce331
  Built:            Fri Apr 14 10:32:17 2023
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.6.20
  GitCommit:        2806fc1057397dbaeefbea0e4e17bddfbd388f38
 runc:
  Version:          1.1.5
  GitCommit:        v1.1.5-0-gf19387a
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Hi @alishah730

Just for your information i’m using nginx:latest image if needed.

Thx

This is very similar to another issue which was related to Kubernetes. I haven’t tried to find that topic, but I will answer the same. It could be an issue with volumes. Depending on how you start the container volumes may not change so even if you changed the image, if you mount an old volume back, you will see the old permissions.

Please, share how you build the image and start the container step by step from the beginning. We don’t have all the information to help you without that. If you can create a small demo that we can try to reproduce the issue you could get an answer much much faster. I am pretty sure there is a simple explanation, but you could have made different mistakes that we don’t even think of, probably because it is so simple.

1 Like

Hi @rimelek, @alishah730

Sorry for the late answer.

Here’s my docker file for building nginx:

FROM nginx:latest

LABEL maintainer="pi3rre.hernandez@gmail.com"

RUN rm /etc/nginx/nginx.conf /etc/nginx/conf.d/default.conf

#install nginx

RUN apt-get update && apt-get upgrade -y \
    && apt install nano curl -y \ 
    && rm -rf /var/lib/apt/lists/*

RUN mkdir -p /var/cache/nginx/client_temp && \
        mkdir -p /var/cache/nginx/proxy_temp && \
        mkdir -p /var/cache/nginx/fastcgi_temp && \
        mkdir -p /var/cache/nginx/uwsgi_temp && \
        mkdir -p /var/cache/nginx/scgi_temp && \
        chown -R nginx:nginx /var/cache/nginx && \
        chown -R nginx:nginx /etc/nginx/ && \
        chmod -R 755 /etc/nginx/ && \
        chown -R nginx:nginx /var/log/nginx

RUN mkdir -p /etc/nginx/ssl/ && \
    chown -R nginx:nginx /etc/nginx/ssl/ && \
    chmod -R 755 /etc/nginx/ssl/

RUN touch /var/run/nginx.pid && \
        chown -R nginx:nginx /var/run/nginx.pid /run/nginx.pid

USER nginx

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

Here’s part of my docker compose:

#LOADBALANCER
  nginxlb:
     container_name: Asgard
     build:
      context: ./
      dockerfile: ./web/nginx/Dockerfile
     depends_on:
       - app1
       - app2
     restart: on-failure:5
     ports:
       - 80:80
       - 443:443
     volumes:
       - ./web/nginx/nginx.conf:/etc/nginx/nginx.conf
       - ./certbot/www:/var/www/certbot:ro
       - ./certbot/conf:/etc/nginx/ssl/:ro
     networks:
      loki:
        ipv4_address: 192.168.123.15
     deploy:
      resources:
        limits:
          cpus: "0.3"
          memory: 500M
     healthcheck:
        test: ["CMD", "service", "nginx", "status"]
        interval: 1m
        timeout: 5s
        retries: 3
  #CERTBOT
  certbot:
     container_name: certbot
     image: certbot/certbot:latest
     depends_on:
       - nginxlb
     volumes:
      - ./certbot/www:/var/www/certbot:rw
      - ./certbot/conf/:/etc/letsencrypt/:rw```


I build the image using the below command:

docker compose up -d

I ran docker daemon using namespace

{
        "icc": false,
        "hosts": ["fd://","unix:///var/run/docker.sock","tcp://0.0.0.0:2376"],
        "userns-remap": "default",
        "userland-proxy": false,
        "iptables" : true,
        "no-new-privileges": true,
        "live-restore": true,
        "tls": true,
        "tlscacert": "/etc/docker/tls/ca.pem",
        "tlscert": "/etc/docker/tls/server-cert.pem",
        "tlskey": "/etc/docker/tls/server-key.pem",
        "tlsverify": true
}

I think that certbot should belonging to dockremap user. Am i right ?
For me, i should change Dockerfile to start nginx container with nobody user since in my container
ssl certificate are owned by this user.

don’t hesitate if you need more information.

Thanks for your help,

As I suspected. If you mount a folder from the host, you need to make sure that the files on the host has the right permissions and ownership. It doesn’t matter what you set during docker build, because you mount a different folder over the original.

Since I rarely use user namespaces, I am always confused regarding what how the remapping works, but I don’t think so. You wanted the ssl fodler to be owned by nginx, so that is what you need to find out what nginx’s ID would be outside the container.

nobody is not a normla user. It means it is a user which is actually nobody in the user namespace, since the ID of the user is not between the minimum and maximum value of the UID. So it is mapped to a UID in the container which is I think something like 65535, but I am not sure. Using this id would probably not help you. Just use the right IDs.

Hi @rimelek

Thanks for your answer :slight_smile:

Blockquote Since I rarely use user namespaces, I am always confused regarding what how the remapping works, but I don’t think so. You wanted the ssl fodler to be owned by nginx, so that is what you need to find out what nginx’s ID would be outside the container.

In fact i have no user with id 101 ( which is nginx user id inside the container ) existing on the host. The only user existing matching user inside my container is www-data user with uid 33.

Blockquote As I suspected. If you mount a folder from the host, you need to make sure that the files on the host has the right permissions and ownership. It doesn’t matter what you set during docker build, because you mount a different folder over the original.

I removed :ro from docker-compose and it does not fix my problem.

It seems you are not aware of what userns-remap is. The point of using a user namespace is that the usr ids in the container and outside containers will be different, so when you mount a folder from the host, a process in the container can’t change anything. You don’t need a UID 101 on the host. You need it in the container, but it is just virtually 101. In reality it is something userns minimum UID + 101 and the minimum UID becomes zero in the container, which means root. This is how rootless containers work too. Your user will run Docker and your user become the root in the container so you can mount files from your home to be owned by root in the container.

Why would it? the problem is not the folder being read-only, but the different UID and GID.

Habe you tried to use the official unprivileged images?

https://hub.docker.com/r/nginxinc/nginx-unprivileged

1 Like

I don’t know how official that image is, but it looks trusted enough when a GitHub staff member is also a contributor so thanks for the suggestion, but can you explain how that would solve the original issue the permissions of the mounted folders?.

Also are you sure you wanted to reply to my comment and not one of @p3dr07’s comments? Just to make sure the right person gets the notification :slight_smile:

Ups sorry for that. I am still new here in the official docker forum.

I think you can trust this images. I got the link from the official nginx description.

Hello @johnnybigodes

No i don’t try this image yet. if i have time i’ll do. Thanks for this suggestion. :wink:

Yes, try it… I am using that image on production.

It runs as user “nginx”.

You can find that link on the offical Nginx page on Docker Hub.

Just scan the Nginx overview for “unprivileged” and you will find it.

I dont know why they are hidding it so much…

Here is a screenshot:

I’m confident this will guide you in deploying your application securely using Nginx.

To ensure security, we need to create a new user for Nginx with a specific user ID (UID), such as 1000. By default, Nginx runs as the root user, which has a UID of 1001.

In the Dockerfile, we’ll create this new user and grant it appropriate permissions. Then, we’ll switch to this user and expose a port greater than 1024, as root users can only bind to ports below 1024.

Afterward, we’ll update the nginx.conf file accordingly.

I’ll provide both files for you.

Dockerfile

FROM nginx:1.25.2

RUN apt-get update && apt-get install -y --no-install-recommends dumb-init

COPY ./nginx.conf /etc/nginx/nginx.conf

RUN adduser --system --uid 1000 --no-create-home --disabled-login --group nginxuser

RUN chown -R nginxuser:nginxuser /var/cache/nginx \
    && chown -R nginxuser:nginxuser /var/log/nginx \
    && chown -R nginxuser:nginxuser /etc/nginx/conf.d \
    && touch /var/run/nginx.pid \
    && chown -R nginxuser:nginxuser /var/run/nginx.pid

USER nginxuser

EXPOSE 8080

ENTRYPOINT ["dumb-init", "nginx", "-g", "daemon off;"]

nginx.conf

worker_processes 1;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    client_body_temp_path /tmp/client_temp;
    proxy_temp_path       /tmp/proxy_temp_path;
    fastcgi_temp_path     /tmp/fastcgi_temp;
    uwsgi_temp_path       /tmp/uwsgi_temp;
    scgi_temp_path        /tmp/scgi_temp;

    server {
        listen       8080;
        server_name  localhost;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
            try_files   $uri $uri/ /index.html =404;
        }
    }
}

This is not necessarily true, on a host with a kernel >= 4.11 and docker version >= 20.03: Non-root user able to bind to port 80; why?