10 KiB
Pangolin Tunnel Deployment
Overview
Pangolin is a self-hosted tunnel service (alternative to Cloudflare Tunnel) that provides public HTTPS access to your Changemaker Lite instance without port forwarding or firewall configuration.
Benefits:
- No port forwarding needed
- SSL/TLS handled by tunnel provider
- Static public URLs
- Self-hosted tunnel server (privacy/control)
- Free/open source
Architecture:
Internet → Pangolin Tunnel (pangolin.bnkserve.org) → Newt Container → Nginx → Services
Changemaker Integration:
- Pangolin server: https://api.bnkserve.org/v1
- Tunnel endpoint: https://pangolin.bnkserve.org
- Newt container: Tunnel connector (fosrl/newt image)
- Admin GUI: PangolinPage.tsx setup wizard
Setup Workflow
1. Prerequisites
Required:
- Pangolin API key (obtain from Pangolin admin)
- Docker Compose running
- Nginx container accessible from Newt
Environment Variables:
PANGOLIN_API_URL=https://api.bnkserve.org/v1
PANGOLIN_API_KEY=<your-api-key>
PANGOLIN_ORG_ID=<set-after-org-creation>
PANGOLIN_SITE_ID=<set-after-site-creation>
PANGOLIN_ENDPOINT=https://pangolin.bnkserve.org
PANGOLIN_NEWT_ID=<set-after-resource-creation>
PANGOLIN_NEWT_SECRET=<set-after-resource-creation>
2. Setup via Admin GUI
Easiest method: Use /app/pangolin page in admin GUI.
Steps:
- Navigate to http://localhost:3000/app/pangolin
- Enter
PANGOLIN_API_KEY(click "Test Connection") - Create Organization (or select existing)
- Create Site (linked to org)
- Create Endpoint (tunnel URL)
- Create Resource (Newt connector credentials)
- Copy
NEWT_IDandNEWT_SECRETto.env - Restart Newt container:
docker compose restart newt
3. Manual Setup (CLI)
Organization:
curl -X POST https://api.bnkserve.org/v1/orgs \
-H "Authorization: Bearer $PANGOLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My Organization",
"description": "Changemaker Lite Production"
}'
# Returns:
# {"id":"org_abc123","name":"My Organization",...}
export PANGOLIN_ORG_ID=org_abc123
Site:
curl -X POST https://api.bnkserve.org/v1/sites \
-H "Authorization: Bearer $PANGOLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"org_id": "'"$PANGOLIN_ORG_ID"'",
"name": "Production Site",
"description": "Main deployment"
}'
# Returns:
# {"id":"site_xyz789","name":"Production Site",...}
export PANGOLIN_SITE_ID=site_xyz789
Endpoint:
curl -X POST https://api.bnkserve.org/v1/endpoints \
-H "Authorization: Bearer $PANGOLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"site_id": "'"$PANGOLIN_SITE_ID"'",
"subdomain": "changemaker",
"domain": "pangolin.bnkserve.org"
}'
# Returns:
# {"id":"endpoint_def456","url":"https://changemaker.pangolin.bnkserve.org",...}
export PANGOLIN_ENDPOINT=https://changemaker.pangolin.bnkserve.org
Resource (Newt Connector):
curl -X POST https://api.bnkserve.org/v1/resources \
-H "Authorization: Bearer $PANGOLIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"endpoint_id": "<endpoint-id>",
"target": "http://nginx:80",
"name": "Changemaker Services"
}'
# Returns:
# {"id":"newt_abc123","secret":"secret_xyz789",...}
export PANGOLIN_NEWT_ID=newt_abc123
export PANGOLIN_NEWT_SECRET=secret_xyz789
Update .env:
cat >> .env <<EOF
PANGOLIN_ORG_ID=$PANGOLIN_ORG_ID
PANGOLIN_SITE_ID=$PANGOLIN_SITE_ID
PANGOLIN_ENDPOINT=$PANGOLIN_ENDPOINT
PANGOLIN_NEWT_ID=$PANGOLIN_NEWT_ID
PANGOLIN_NEWT_SECRET=$PANGOLIN_NEWT_SECRET
EOF
4. Start Newt Container
# Restart to pick up new env vars
docker compose up -d --force-recreate newt
# Check logs
docker compose logs -f newt
# Should see:
# [INFO] Connected to Pangolin tunnel
# [INFO] Tunnel active: https://changemaker.pangolin.bnkserve.org
5. DNS Configuration
Option A: Direct Access (use tunnel URL):
Option B: Custom Domain (CNAME to tunnel):
app.cmlite.org. CNAME changemaker.pangolin.bnkserve.org.
api.cmlite.org. CNAME changemaker.pangolin.bnkserve.org.
Newt Container Configuration
docker-compose.yml:
newt:
image: fosrl/newt
container_name: newt-changemaker
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=${PANGOLIN_ENDPOINT}
- NEWT_ID=${PANGOLIN_NEWT_ID}
- NEWT_SECRET=${PANGOLIN_NEWT_SECRET}
depends_on:
- nginx
networks:
- changemaker-lite
Key Features:
- Restart policy:
unless-stopped(auto-reconnects) - Nginx dependency: Ensures Nginx running before Newt starts
- No ports exposed: All traffic via tunnel
Target: Newt connects to http://nginx:80 (Docker internal network).
Tunnel Lifecycle
Start Tunnel
docker compose up -d newt
Stop Tunnel
docker compose stop newt
Check Status
# Container status
docker compose ps newt
# Logs
docker compose logs --tail=50 newt
# Test tunnel
curl https://changemaker.pangolin.bnkserve.org/api/health
Restart (after .env changes)
docker compose up -d --force-recreate newt
Exit Nodes & Resource Routing
Resource: Defines how tunnel routes traffic to your services.
Target URL: http://nginx:80 (Nginx handles subdomain routing internally).
Example Flow:
- User visits
https://changemaker.pangolin.bnkserve.org/api/health - Pangolin tunnel receives HTTPS request
- Tunnel forwards to Newt container
- Newt proxies to
http://nginx:80/api/health - Nginx routes to
changemaker-v2-api:4000/api/health - Response flows back through tunnel
Multiple Resources: Create separate resources for different backends (advanced).
Troubleshooting
Tunnel Not Connecting
Symptoms: Newt logs show connection errors
Diagnosis:
docker compose logs newt
# Common errors:
# - "Authentication failed" (wrong NEWT_ID/SECRET)
# - "Endpoint not found" (wrong PANGOLIN_ENDPOINT)
# - "Connection refused" (Nginx not running)
Solutions:
# Verify credentials
docker compose exec newt printenv | grep PANGOLIN
# Test Nginx from Newt container
docker compose exec newt wget -O- http://nginx:80/api/health
# Restart Newt
docker compose restart newt
Tunnel Connected But Site Unreachable
Symptoms: Newt connected, but HTTPS requests timeout/fail
Diagnosis:
# Test tunnel endpoint
curl -I https://changemaker.pangolin.bnkserve.org
# Check Nginx logs
docker compose logs nginx | tail -50
# Verify resource target
curl -X GET https://api.bnkserve.org/v1/resources/<resource-id> \
-H "Authorization: Bearer $PANGOLIN_API_KEY"
Common Causes:
- Resource target points to wrong service
- Nginx not listening on port 80
- Firewall blocking Nginx → backend communication
Solution:
# Verify Nginx config
docker compose exec nginx nginx -t
# Check Nginx listening
docker compose exec nginx netstat -tulpn | grep :80
# Test backend from Nginx
docker compose exec nginx curl http://changemaker-v2-api:4000/api/health
SSL Certificate Errors
Symptoms: Browser shows "Certificate invalid" warning
Cause: Tunnel endpoint SSL certificate not trusted (rare).
Solution: Contact Pangolin support — tunnel provider manages SSL certificates.
Frequent Disconnects
Symptoms: Newt reconnects every few minutes
Diagnosis:
# Check for network issues
docker compose logs newt | grep -i disconnect
# Monitor connection
watch -n5 'docker compose logs --tail=1 newt'
Possible Causes:
- Network instability
- Container restarts (check
docker compose ps) - Resource limits (check
docker stats newt-changemaker)
Solution:
# Increase restart backoff (if needed)
# Edit docker-compose.yml:
newt:
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3
Migration from Cloudflare Tunnel
Retired Scripts (in scripts/legacy/):
start-production.shconfig.shtunnel-config.sh
Migration Steps:
- Stop Cloudflare tunnel:
cloudflared service uninstall - Remove Cloudflare credentials:
rm ~/.cloudflared/*.json - Setup Pangolin tunnel (see above)
- Update DNS: Change CNAME from
cloudflared.comtopangolin.bnkserve.org - Test new tunnel:
curl https://changemaker.pangolin.bnkserve.org/api/health - Remove old scripts:
rm scripts/legacy/*
Why Pangolin?
- Self-hosted (privacy/control)
- No Cloudflare dependency
- Free/open source
- API-driven management
Advanced Configuration
Custom Tunnel Domain
Requirement: Own domain with DNS control.
Steps:
- Create endpoint with custom domain
- Add DNS record:
tunnel.cmlite.org CNAME pangolin.bnkserve.org. - Update
PANGOLIN_ENDPOINT=https://tunnel.cmlite.org - Restart Newt
Multiple Sites
Use case: Staging + production on same tunnel.
Setup:
# Create second site
curl -X POST https://api.bnkserve.org/v1/sites \
-d '{"org_id":"...","name":"Staging"}'
# Create endpoint for staging
curl -X POST https://api.bnkserve.org/v1/endpoints \
-d '{"site_id":"...","subdomain":"staging-changemaker"}'
# Create resource pointing to staging Nginx
curl -X POST https://api.bnkserve.org/v1/resources \
-d '{"endpoint_id":"...","target":"http://nginx-staging:80"}'
Monitoring
Health Checks
Tunnel status:
# Container health
docker compose ps newt
# Connection logs
docker compose logs --tail=50 newt | grep -i connected
# Test public endpoint
curl -I https://changemaker.pangolin.bnkserve.org
Prometheus metrics (if enabled):
# API uptime through tunnel
curl https://changemaker.pangolin.bnkserve.org/api/metrics | grep cm_api_uptime
Related Documentation
- Docker Compose — Newt container configuration
- Nginx Configuration — Reverse proxy setup
- SSL/TLS — Certificate management (handled by tunnel)
- Environment Variables — Pangolin env vars