Setting up Nginx Proxy Manager (npm) on docker

1cd
2mkdir docker-compose/npm; cd npm
3mkdir data
4mkdir letsencrypt

Create the following file as docker-compose.yaml

 1services:
 2  app:
 3    image: 'docker.io/jc21/nginx-proxy-manager:latest'
 4    container_name: npm
 5    restart: unless-stopped
 6    ports:
 7      # HTTP port
 8      - 80:80
 9      # HTTPS Port:
10      - 443:443
11      # Admin UI
12      - 10.0.0.1:81:81
13    environment:
14      DB_SQLITE_FILE: "/data/npm.sqlite"
15    volumes:
16      - ./data:/data
17      - ./letsencrypt:/etc/letsencrypt
18    networks:
19      - seafile_seafile-npm
20
21networks:
22    seafile_seafile-npm:
23      external: true

Then docker-compose up -d to run the container.

Post install

When logging in for the first time, use credentials

Email:    [email protected]
Password: changeme

You’ll need to change the credentials immediately after first login.

Comment: If a reverse proxied service shows a 502 bad gateway error, it is almost always because npm container can not access the ip_addr/name:port of the service being reverse proxied.

Notes

  1. Binding the admin UI at port 81 to the Wireguard interface 10.0.0.1 only. This means that the admin interface is only accessible from a wireguard client. Currently, this is my home Linux Desktop machine. There is no need to expose this UI to the world.
  2. The seafile_seafile-npm network needs some explanation. Basically, Nginx Proxy Manager can only access Seafile if
    1. npm and seafile are on the same network: This means, either put all containers that need to be reverse proxied on the same network OR create a dedicated network for each container to share with npm.
      • The former method essentially breaks network isolation across containers (all containers can see each other if they are on the same network. If one container gets compromised, potentially other containers get compromised too. The way around this is to use something like TRAFFICJAM (see References) which uses iptables to ensure that only container<>npm connections are allowed.).
      • The latter approach leads to the creation of at least one network per container-that-needs-to-be-proxied. That’s .. icky, but the approach taken here, because it is simple. Will revise it if there are many containers and this becomes unwieldy or starts showing performance issues. So we create a network in Seafile’s docker-compose named seafile-npm (more generally, appname-npm) and add npm to this network as shown above. We start the Seafile docker container first so that npm can find the external: true seafile_seafile-npm network. If we start npm docker first, it’ll complain that the seafile_seafile-npm network isn’t available. Conversely, if we shut down seafile docker container before npm is shut down (very likely scenario), note that the seafile_seafile-npm network will not be shut down because npm is “using it”. Then, when npm is shut down, it will still not shut down the seafile_seafile-npm network because it is an external network. So to bring down this network we’d need to do docker-compose down first in seafile, then in npm, then again in seafile. /shrug. Note further that with this approach, there is no need to expose ports in other containers to the host i.e. no port mapping needs to be defined. NPM can look up other containers using their defined container_name and connect to the ports inside the container.
    2. npm has access to the hosts 127.0.0.1 so that it can proxy a request to 127.0.0.1:port_num.
      • One way to do this is by selecting network_mode to host i.e. the npm container runs on the hosts network stack. Then, npm will have access to 127.0.0.1:port_num. However, it means that no port-bindings e.g. 80:80 are possible, since that makes no sense. In that case, the npm admin UI at port 81 gets exposed to ALL host interfaces and the only thing that would prevent the world from accessing the admin interface would be a firewall rule (either on the host or through the VPS e.g. Hetzner firewall). I haven’t yet figured out how npm’s port 81 could be bound only to the Wireguard interface, when npm docker is run with host networking.

      • The other way to do this would be to use

        extra_hosts:
            - "host.docker.internal:host-gateway"
        

        in the docker-compose.yaml file, which presumably gives the npm container access to localhost on the host i.e. host.docker.internal:port_num would be 127.0.0.1:port_num on the host. But I never got this to work. It seems that npm would not resolve host.docker.internal to localhost.

        • An alternative to the host-gateway would be to manually set the ip address of the docker0 interface on the host, typically 172.17.0.1, in place of host-gateway in the above. I tried that, but it didn’t work either.

References

  1. https://github.com/kaysond/trafficjam <– A firewall that prevents containers from talking to each other when they are on the same docker network
  2. https://nginxproxymanager.com/setup/#running-the-app <– Official setup guide