Setting up secure, publicly accessible services from a local development environment can be challenging, especially when dealing with multiple layers of reverse proxies, SSL certificates, and container networking. This post walks through a common scenario many developers face and provides a clean solution for exposing local HTTPS services through Pangolin tunnel service.
The Setup: Local HTTPS with Real Certificates
The goal was straightforward: run multiple Docker Compose projects on a local Linux server, make them accessible via HTTPS with real domain names locally, and then expose selected services to the internet through Pangolin.
Here’s the initial architecture:
Local Environment:
- Multiple Docker Compose projects running various services
- Caddy reverse proxy providing HTTPS termination
- Real SSL certificates via Cloudflare DNS challenge
- Domain names added to
/etc/hosts
for local resolution
Public Access:
- Pangolin installed via Docker on a VPS
- Newt agent connecting the local server to Pangolin
This setup worked perfectly for local access. Services were available at https://my-service.example.com
locally with valid SSL certificates.
The Challenge: DNS Resolution in Pangolin
The problem arose when trying to expose these services through Pangolin. Initially, when services used HTTP with IP addresses and specific ports, Pangolin resources were configured like:
Target: http://192.168.1.100:3000
After implementing HTTPS with domain names, the natural approach was to update Pangolin resources to:
Target: https://my-service.example.com:443
However, Pangolin couldn’t resolve the domain names because they only existed in local /etc/hosts
files, not in public DNS.
The Solution: Docker Network Integration
The breakthrough came from rethinking the network architecture. Instead of trying to make Pangolin resolve external domain names, the solution was to put the target containers in the same Docker network as newt.
Implementation
- Create a shared Docker network or use newt’s existing network
- Connect target service containers to this shared network
- Configure Pangolin resources to target containers directly by name
With this approach, Pangolin resources can be configured as:
Target: https://my-service-container:8443
Why This Works
This solution works because:
- Direct container communication: newt and target containers are on the same Docker network
- DNS resolution: Docker’s internal DNS resolves container names automatically
- Bypasses Caddy: Traffic goes directly to the service container, avoiding the reverse proxy layer
- Maintains HTTPS: Services can still use HTTPS on their internal ports
Network Configuration Example
# docker-compose.yml for your service
services:
my-service:
image: my-app:latest
networks:
- pangolin-network
ports:
- "8443:8443" # HTTPS port for the service
networks:
pangolin-network:
external: true # Connect to newt's network
Benefits of This Approach
- Simplified routing: Direct container-to-container communication
- No DNS hacks: No need for
/etc/hosts
manipulation in containers - Maintains security: HTTPS can still be used between containers
- Scalable: Easy to add new services to the same network
- Clean separation: Local reverse proxy (Caddy) and public tunnel (Pangolin) serve different purposes
Conclusion
When working with containerized applications and tunnel services like Pangolin, understanding Docker networking is crucial. Rather than fighting DNS resolution issues, leveraging Docker’s built-in networking capabilities provides a cleaner, more maintainable solution.
The key insight is that not every layer needs to go through your reverse proxy. By putting target containers on the same network as your tunnel agent, you can create direct, secure connections while maintaining the benefits of your local HTTPS setup for development and testing.
This approach allows you to maintain the best of both worlds: a sophisticated local development environment with real SSL certificates and clean, direct public access through your tunnel service.