Complete Guide: Secure Application Deployment with Pangolin and NPM

Complete Guide: Secure Application Deployment with Pangolin(Cloud VPS) and NPM(Home Deployment)

This comprehensive guide will walk you through setting up a secure application infrastructure using Pangolin for tunneling and Nginx Proxy Manager (NPM) for reverse proxying.

We’ll break this into two main parts.

Part 1: Pangolin Configuration

Let’s start by setting up the Pangolin side of our infrastructure.

Initial Pangolin Setup

First, we need to create a site and set up the proper resources in Pangolin.

Creating a Site

  1. Log into your Pangolin dashboard
  2. Navigate to Sites and select “Add Site”
  3. Fill in the following details:
    Name: Internal Applications
    Description: Hosts internal services behind NPM
    Connection Method: Newt (recommended for better stability)
    
  4. After creation, you’ll receive Newt credentials. Save these securely:
    PANGOLIN_ENDPOINT=your-pangolin-instance.com
    NEWT_ID=generated-id
    NEWT_SECRET=generated-secret
    

Setting Up Resources

For each application, we need to create a corresponding resource in Pangolin. Let’s set up resources for common applications:

Uptime Kuma
  1. Go to Resources → Add Resource
  2. Configure:
    Name: Uptime Kuma
    Subdomain: uptime (will create uptime.yourdomain.com)
    Site: Internal Applications
    
    Under Connectivity:
    - Enable SSL: Yes
    - Method: HTTP
    - IP/Hostname: 10.24.7.96
    - Port: 89
    

Read down for explanation.

Nextcloud Resource
  1. Go to Resources → Add Resource
  2. Configure:
    Name: Nextcloud
    Subdomain: cloud (will create cloud.yourdomain.com)
    Site: Internal Applications
    
    Under Connectivity:
    - Enable SSL: Yes
    - Method: HTTP
    - IP/Hostname: nginx-proxy-manager
    - Port: 80
    

Gitea Resource
  1. Add another resource:
    Name: Gitea
    Subdomain: git
    Site: Internal Applications
    
    Under Connectivity:
    - Enable SSL: Yes
    - Method: HTTP
    - IP/Hostname: nginx-proxy-manager
    - Port: 80
    

WordPress Resource
  1. Add resource:
    Name: WordPress
    Subdomain: blog
    Site: Internal Applications
    
    Under Connectivity:
    - Enable SSL: Yes
    - Method: HTTP
    - IP/Hostname: nginx-proxy-manager
    - Port: 80
    

Authentication Configuration

For each resource, configure authentication under the Authentication tab:

  1. Platform SSO:

    Use Platform SSO: Enabled
    Allow Public Access: Disabled
    
  2. Additional Authentication (optional):

    Email Whitelist: Add approved email domains
    Pin Code: Set up for quick access
    Custom Password: For shared access
    

SSL Configuration

In Pangolin’s Traefik configuration, ensure proper SSL handling:

  1. Edit traefik_config.yml:
    certificatesResolvers:
      letsencrypt:
        acme:
          email: your-email@domain.com
          storage: /letsencrypt/acme.json
          httpChallenge:
            entryPoint: web
    
    entryPoints:
      web:
        address: ":80"
      websecure:
        address: ":443"
        http:
          tls:
            certResolver: letsencrypt
    

Part 2: NPM and Application Deployment

Now let’s set up the local infrastructure with NPM and our applications.

Network and Base Infrastructure

First, create the required Docker network:

docker network create npm_network --external

External Application Compose Configuration

Create a Uptime Kuma docker-compose.yml file:

services:
  uptime-kuma:
    image: louislam/uptime-kuma:1
    container_name: uptime-kuma
    restart: always
    ports:
      - "3010:3001"
    volumes:
      - ./uptime-kuma:/app/data
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - npm_network

volumes:
  uptime-kuma:

networks:
  npm_network:
    name: npm_network

Docker Compose Configuration

Create a docker-compose.yml file:

networks:
  npm_network:
    external: true 

volumes:
  npm_data:
  npm_letsencrypt:
  nextcloud_data:
  gitea_data:
  wordpress_data:
  wordpress_db:

services:
  nginx-proxy-manager:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    restart: unless-stopped
    ports:
      - '89:80'
      - '449:443'
      - '81:81'
    environment:
      DISABLE_DEFAULT_SERVER: "true"
      LE_EMAIL: "your-email@domain.com"
    volumes:
      - npm_data:/data
      - npm_letsencrypt:/etc/letsencrypt
    networks:
      - npm_network

  newt:
    image: fosrl/newt:latest
    container_name: newt
    restart: unless-stopped
    environment:
      - PANGOLIN_ENDPOINT=your-pangolin-instance.com
      - NEWT_ID=your-newt-id
      - NEWT_SECRET=your-newt-secret
    networks:
      - npm_network

  # Application Services
  nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    restart: unless-stopped
    volumes:
      - nextcloud_data:/var/www/html
    networks:
      - npm_network

  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    restart: unless-stopped
    volumes:
      - gitea_data:/data
    networks:
      - npm_network

  wordpress:
    image: wordpress:latest
    container_name: wordpress
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: wordpress-db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: secure_password
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress_data:/var/www/html
    networks:
      - npm_network

  wordpress-db:
    image: mariadb:latest
    container_name: wordpress-db
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: secure_password
      MYSQL_ROOT_PASSWORD: very_secure_root_password
    volumes:
      - wordpress_db:/var/lib/mysql
    networks:
      - npm_network

NPM Proxy Host Configuration

Access NPM’s admin interface (port 81) and configure each application:

Uptime Kuma Proxy Host

Domain Names: uptime.yourdomain.com
Scheme: http
Forward Hostname: 10.24.7.96
Forward Port: 3010
Advanced: Enable WebSocket Support

Nextcloud Proxy Host

Domain Names: cloud.yourdomain.com
Scheme: http
Forward Hostname: nextcloud
Forward Port: 80
Advanced: Enable WebSocket Support

Nextcloud Configuration
location / {
    proxy_pass http://nextcloud:80;
    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-Proto $scheme;
    
    # Nextcloud-specific headers
    proxy_set_header X-Forwarded-Host $host;
    client_max_body_size 512M;
    
    # WebDAV support
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

Gitea Proxy Host

Domain Names: git.yourdomain.com
Scheme: http
Forward Hostname: gitea
Forward Port: 3000
Advanced: Enable WebSocket Support


Gitea Configuration

location / {
    proxy_pass http://gitea:3000;
    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-Proto $scheme;
    
    # Git-specific settings
    proxy_set_header X-Forwarded-Host $host;
    client_max_body_size 512M;
    
    # WebSocket support for Gitea
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

WordPress Proxy Host

Domain Names: blog.yourdomain.com
Scheme: http
Forward Hostname: wordpress
Forward Port: 80

WordPress Configuration

location / {
    proxy_pass http://wordpress:80;
    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-Proto $scheme;
    
    # WordPress-specific settings
    client_max_body_size 128M;
    proxy_read_timeout 90;
}


Starting the Infrastructure

  1. Launch the stack:

    docker-compose up -d
    
  2. Verify services:

    docker-compose ps
    
  3. Check Newt connection:

    docker logs newt
    

Imp-- Pangolin and NPM Integration - Understanding Network Architecture

When connecting services through Pangolin and NPM, we need to consider two distinct scenarios: services that run inside our Docker network stack and services that run outside of it. Let’s understand both approaches.

Internal Services (Within Docker Network)

When your applications run inside the same Docker network as NPM, they communicate using Docker’s internal networking. In this case:

networks:
  npm_network:
    external: true 

services:
  nginx-proxy-manager:
    image: 'jc21/nginx-proxy-manager:latest'
    container_name: nginx-proxy-manager
    ports:
      - '89:80'      # External mapping
      - '449:443'    # External mapping
      - '81:81'      # Admin interface
    networks:
      - npm_network

  nextcloud:
    image: nextcloud:latest
    container_name: nextcloud
    networks:
      - npm_network    # Same network as NPM

In Pangolin, configure your resource like this:

Name: Nextcloud
Target Configuration:
- Method: HTTP
- IP/Hostname: nginx-proxy-manager
- Port: 80    # Internal Docker port, not the mapped port

This works because both containers can communicate directly through Docker’s internal network, where NPM listens on its default port 80.

External Services (Outside Docker Network)

For services running outside the Docker network (like on your local network or another server), we need to use the external mapped ports:

services:
  nginx-proxy-manager:
    image: 'jc21/nginx-proxy-manager:latest'
    ports:
      - '89:80'    # This mapping becomes important

In Pangolin, configure your resource like this:

Name: External Service
Target Configuration:
- Method: HTTP
- IP/Hostname: 10.24.7.96    # Actual IP of the server
- Port: 89                   # External mapped port

Here, we use port 89 because the service must connect through NPM’s external interface, not through Docker’s internal networking.

Understanding the Difference

Let’s break down why this matters:

  1. Internal Services:

    • Use container names for hostnames
    • Use default ports (80, 443)
    • Benefit from Docker’s DNS resolution
    • More secure as traffic stays within Docker network
  2. External Services:

    • Use actual IP addresses
    • Use mapped ports (89, 449)
    • Must consider network routing
    • Traffic flows through external network interfaces

Here’s a practical example combining both approaches:

services:
  nginx-proxy-manager:
    image: 'jc21/nginx-proxy-manager:latest'
    ports:
      - '89:80'
      - '449:443'
    networks:
      - npm_network

  # Internal service example
  wordpress:
    image: wordpress:latest
    networks:
      - npm_network

# Pangolin configurations:

# For WordPress (internal)
Resource 1:
  Name: WordPress
  Target: nginx-proxy-manager:80

# For External App(Uptime Kuma)
Resource 2:
  Name: UPTIME
  Target: 10.24.7.96:89

This understanding helps you properly configure your resources in Pangolin based on where your services are located relative to your NPM installation. Remember: internal services use port 80, external services use port 89.

The key is remembering that Docker’s internal networking operates independently of the port mappings we create for external access. This is why internal services can use the default ports while external services must use the mapped ports.

Post-Deployment Verification

  1. Access each application through its domain
  2. Verify SSL certificates are working
  3. Test Pangolin authentication
  4. Confirm application functionality

Maintenance Procedures

Regular maintenance tasks:

  1. Update containers:

    docker-compose pull
    docker-compose up -d
    
  2. Backup volumes:

    docker run --rm -v npm_data:/data -v /backup:/backup alpine tar czf /backup/npm-backup.tar.gz /data
    
  3. Monitor logs:

    docker-compose logs -f
    

This setup provides a secure, maintainable infrastructure for hosting multiple applications behind Pangolin’s secure tunnel and NPM’s reverse proxy. The configuration can be extended to add more applications as needed, with each benefiting from the central authentication and SSL termination provided by Pangolin.

This looks great. Can I do this with Traefik instead of NPM or does that conflict with the Traefik instance that lives in Pangolin on the VPS? Mind that I’d setup Traefik on Unraid (Newt is also on Unraid, rest of Pangolin on the VPS).

yes you can do that, or with any proxy. remember ssl termination matters the most and you will have to handles your domains accordingly.