Pangolin
How I use Pangolin for access control and reverse proxying across multiple servers, with sanitized Compose and configuration examples.
How I Use Pangolin Across My Servers
I use Pangolin for access control and reverse proxying across multiple servers. The applications stay on the machines where they run; Pangolin gives me a central place to manage how traffic reaches them.
The Pangolin stack runs on d4rkcloud with Gerbil and Traefik. Newt connectors run on the other servers and connect those sites back to Pangolin.
Services Behind Pangolin
The setup covers a mix of personal, home, and business services:
- docker-vm: D4rk Notes, Immich, Home Assistant
- d4rkcloud-2: Abmye Farms, Unistrut, DocuSeal
Pangolin handles the route and access policy; the applications remain distributed across the servers.
The Pangolin Compose Stack
This is the central stack on d4rkcloud. The hostname is configurable, and credentials are kept out of the Compose file. The example preserves the running service layout and pins the Pangolin, Gerbil, and Traefik images.
name: pangolin
services:
pangolin:
image: fosrl/pangolin:1.25.3
container_name: pangolin
restart: unless-stopped
volumes:
- ./config:/app/config
- ./assets:/assets
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: 10s
timeout: 10s
retries: 15
logging:
driver: json-file
options:
max-size: "20m"
max-file: "10"
labels:
- "traefik.enable=true"
- "traefik.http.routers.pangolin.rule=Host(`${PANGOLIN_HOST}`)"
- "traefik.http.routers.pangolin.entrypoints=websecure"
- "traefik.http.routers.pangolin.tls=true"
- "traefik.http.routers.pangolin.tls.certresolver=letsencrypt"
- "traefik.http.services.pangolin.loadbalancer.server.port=3001"
gerbil:
image: docker.io/fosrl/gerbil:1.4.3
container_name: gerbil
restart: unless-stopped
depends_on:
pangolin:
condition: service_healthy
command:
- --reachableAt=http://gerbil:3004
- --generateAndSaveKeyTo=/var/config/key
- --remoteConfig=http://pangolin:3001/api/v1/
volumes:
- ./config/:/var/config
cap_add:
- NET_ADMIN
- SYS_MODULE
ports:
- "51830:51830/udp"
- "21820:21820/udp"
- "443:443"
- "80:80"
networks:
- proxy
- default
traefik:
image: docker.io/traefik:v3.6.2
container_name: traefik
restart: unless-stopped
network_mode: service:gerbil
env_file:
- .env
depends_on:
pangolin:
condition: service_healthy
command:
- --configFile=/etc/traefik/traefik_config.yml
volumes:
- ./config/traefik:/etc/traefik:ro
- ./config/letsencrypt:/letsencrypt
- ./config/traefik/logs:/var/log/traefik
logging:
driver: json-file
options:
max-size: "20m"
max-file: "10"
networks:
default:
driver: bridge
name: pangolin
proxy:
name: proxy
external: trueCentral Stack Environment Example
The live .env file contains a Cloudflare DNS token used by Traefik’s DNS challenge. Keep the real file local and protected; this example uses placeholders only.
# .env.example — Pangolin host
PANGOLIN_HOST=your-pangolin-hostname
CF_DNS_API_TOKEN=replace-with-your-cloudflare-dns-tokenPangolin Configuration Example
The deployed configuration file on this stack is named config.yml. This sanitized config.yaml example shows the same main settings without copying the live server secret, SMTP password, or real hostnames.
gerbil:
start_port: 51830
base_endpoint: "your-pangolin-hostname"
app:
dashboard_url: "https://your-pangolin-hostname"
log_level: "debug"
telemetry:
anonymous_usage: true
domains:
domain1:
base_domain: "your-domain.example"
server:
secret: "REPLACE_WITH_A_GENERATED_SECRET"
cors:
origins:
- "https://your-pangolin-hostname"
methods:
- GET
- POST
- PUT
- DELETE
- PATCH
allowed_headers:
- X-CSRF-Token
- Content-Type
credentials: false
maxmind_db_path: "./config/GeoLite2-Country.mmdb"
integration_port: 3003
flags:
require_email_verification: false
disable_signup_without_invite: true
disable_user_create_org: false
allow_raw_resources: true
enable_integration_api: true
email:
smtp_host: "your-smtp-host"
smtp_port: 587
smtp_user: "your-smtp-user"
smtp_pass: "REPLACE_WITH_SMTP_PASSWORD"
no_reply: "no-reply@example.invalid"
smtp_secure: false
smtp_tls_reject_unauthorized: trueReplace the placeholders with values for your own deployment. In particular, generate a unique server secret and set the SMTP password locally. Do not publish a live configuration file.
Newt Connector on Each Server
Each remote server runs its own Newt connector. The Compose service shape is reusable, but the site ID and secret must be unique for each registered site.
services:
newt:
image: fosrl/newt
container_name: newt
restart: unless-stopped
environment:
PANGOLIN_ENDPOINT: ${PANGOLIN_ENDPOINT:?set the Pangolin endpoint}
NEWT_ID: ${NEWT_ID:?set this site's ID}
NEWT_SECRET: ${NEWT_SECRET:?set this site's secret}
volumes:
- ./data:/root/.config/newt-client
networks:
- proxy
networks:
proxy:
name: proxy
external: trueUse a separate local environment file for each Newt site:
# .env.example — make a separate .env for each server
PANGOLIN_ENDPOINT=https://your-pangolin-hostname
NEWT_ID=replace-with-this-sites-id
NEWT_SECRET=replace-with-this-sites-secretI keep the actual Newt credentials out of the article and out of source control. Each server gets the credentials for its own site.
How I Think About the Traffic Path
The reverse proxy routes a request to the configured service target. Newt connects the remote site to Pangolin, and the access policy determines who is allowed through. Those are separate layers: routing gets traffic to an application; access control governs who can reach it.
That distinction matters when troubleshooting. A service can be running while its route is wrong, or the route can work while an access policy prevents the expected user from getting in. I check the application, the site connection, the resource target, and the access policy individually.
Why This Layout Works for Me
I don’t have to move every application onto the Pangolin server or maintain a separate public-entry stack on every machine. The services stay distributed across multiple servers, while access control and reverse proxying are managed centrally.
For my setup, Pangolin ties together access control and reverse proxying across multiple servers. The applications stay where they are; the way I manage access stays in one place.