VM 102 · Docker Stack
NPM handles all reverse proxy routing for*.munyakazi.orgsubdomains - forwarding incoming traffic from the Cloudflare Tunnel to the correct container on the internal Docker network.
| Admin UI | http://192.168.86.53:81 |
| Image | jc21/nginx-proxy-manager:latest |
| Version | 2.15.1 |
| Status | Live |
Nginx Proxy Manager is a Docker container that wraps Nginx with a clean web GUI - allowing you to manage reverse proxy hosts, SSL certificates, and access lists without writing Nginx config files manually. It sits between the Cloudflare Tunnel and all other containers, routing each subdomain to the correct service.
All services run on internal Docker ports (Nextcloud on 8080, Wiki.js on 3000, Portainer on 9443). NPM maps each public subdomain to the right internal container by name, using Docker's internal DNS resolution across the shared npm_default network.
version: "3.8"
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "80:80"
- "81:81"
- "443:443"
volumes:
- npm_data:/data
- npm_letsencrypt:/etc/letsencrypt
environment:
DB_SQLITE_FILE: "/data/database.sqlite"
volumes:
npm_data:
npm_letsencrypt:
Uses SQLite (built-in) - no separate database container needed for NPM's own config storage.
| Domain | Scheme | Forward To | Port |
|---|---|---|---|
| cloud.munyakazi.org | http | nextcloud | 80 |
| portainer.munyakazi.org | https | portainer | 9443 |
| wiki.munyakazi.org | http | wikijs | 3000 |
NPM is the central hub of the npm_default Docker network. Every container that needs external access joins this network, allowing NPM to reach them by container name (Docker's internal DNS).
npm_default network:
├── npm (the proxy itself)
├── cloudflared (receives tunnel traffic, forwards to npm:80)
├── nextcloud (forwarded from cloud.munyakazi.org)
├── portainer (forwarded from portainer.munyakazi.org)
└── wikijs (forwarded from wiki.munyakazi.org)
Database containers (nextcloud-db, wikijs-db) are intentionally excluded from this network.
TLS/HTTPS is terminated at Cloudflare's edge - not at NPM. The connection from cloudflared to NPM is plain HTTP on port 80 internally. NPM's SSL tab is set to "None" for all proxy hosts.
This is correct and intentional: Cloudflare handles the public-facing certificate and encrypts traffic between the user and Cloudflare's edge. The internal tunnel-to-NPM hop is already isolated inside the Docker network.
The exception is Portainer, which only serves HTTPS on port 9443 internally. NPM connects to Portainer using https scheme with proxy_ssl_verify off in the custom Nginx config to accept the self-signed certificate.
Since Portainer uses a self-signed certificate internally, the NPM proxy host for portainer.munyakazi.org uses:
https9443proxy_ssl_verify off;
| Issue | Cause | Fix |
|---|---|---|
| 502 Bad Gateway on portainer.munyakazi.org | Cloudflare tunnel using https://npm:443 - SNI mismatch | Changed tunnel route to http://npm:80 |
| portainer container unreachable from NPM | portainer on bridge network, not npm_default | Joined npm_default via Portainer container detail page |
| Nextcloud 502 after adding reverse proxy | nextcloud not on npm_default network | Updated nextcloud stack compose to join npm_default |