Bypass DS-Lite: Reverse Proxy with Docker & Pangolin

Learn how to bypass DS-Lite with a tunnel and securely access your local services using a reverse proxy with Docker and Pangolin.

Bypass DS-Lite: Reverse Proxy with Docker & Pangolin-heroimage

In the age of DS-Lite (Dual Stack Lite), it’s becoming increasingly difficult to access your own services—like websites, APIs, or other applications—from the internet. Why? Because many internet providers no longer assign public IPv4 addresses. But don’t worry—this article will show you how to solve this problem! With the open-source reverse proxy Pangolin, you can create a secure tunnel between your local server and an external VPS. This way, you can access your services from anywhere in the world.

Why DS-Lite Is a Problem

Before we dive into the solution, let’s quickly review why DS-Lite is such a hurdle:

  1. No Public IPv4 Address: With DS-Lite, you don’t get your own public IPv4 address. This means your router isn’t directly accessible from the internet.
  2. Restricted Remote Access: Without a public IP address, you can’t open ports to reach services like a web server or other applications from outside your network.
  3. IPv6 as an Alternative? Theoretically, you could use IPv6, but many networks and devices still don’t fully support it. Plus, configuring IPv6 can be tricky and unreliable.

The solution is simple: Use an external VPS (virtual private server) as a bridge between your local network and the internet. Sounds like a lot of work? It’s not—if you know how to do it.

What Is a Reverse Proxy?

A reverse proxy is a server that accepts requests from the outside and forwards them to the correct service in your local network. Think of it like a post office: You send a letter to the post office, and they make sure it reaches the right recipient.

With Pangolin, this process becomes even easier. Pangolin is an open-source tool specifically designed to create secure tunnels between your local server and an external VPS. It uses modern encryption and is incredibly easy to configure.

What You Need for This Setup

Before we begin, here’s a quick list of what you’ll need:

  1. A Local Server: This could be a Raspberry Pi, an old PC, or any other machine running your services.
  2. An External VPS: A virtual server with a public IP address. I recommendNetcup, as they offer affordable plans starting at just 1 € per month.
  3. Domain: A domain where you can adjust DNS settings. Again, Netcup offers domains for as little as 0.42 € per month.
  4. Basic Linux Knowledge: You should know how to connect to a server via SSH and execute basic commands.

Would you prefer a German video tutorial with all the steps? Check out this YouTube video.

Step-by-Step: Setting Up Your Reverse Proxy with Pangolin

Prepare Your Local Server

First, set up your local server. Install Docker and Docker Compose if you haven’t already.

Rent and Set Up a VPS

Next, rent a VPS with Ubuntu as the operating system. After connecting to the server via SSH, update the system and install Docker.

sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/raspbian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/raspbian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Install Pangolin

Create the necessary folders for Pangolin and its configuration files.

mkdir -p pangolin/config
touch pangolin/config/traefik_config.yaml
touch pangolin/config/dynamic_config.yml
touch pangolin/docker-compose.yml

Customize these files to suit your needs, especially the domain.

docker-compose.yml:

services:
    pangolin:
        image: fosrl/pangolin:1.0.0-beta.14
        container_name: pangolin
        restart: unless-stopped
        volumes:
            - ./config:/app/config
        healthcheck:
            test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
            interval: "3s"
            timeout: "3s"
            retries: 5

    gerbil:
        image: fosrl/gerbil:1.0.0-beta.3
        container_name: gerbil
        restart: unless-stopped
        depends_on:
            pangolin:
                condition: service_healthy
        command:
            - --reachableAt=http://gerbil:3003
            - --generateAndSaveKeyTo=/var/config/key
            - --remoteConfig=http://pangolin:3001/api/v1/gerbil/get-config
            - --reportBandwidthTo=http://pangolin:3001/api/v1/gerbil/receive-bandwidth
        volumes:
            - ./config/:/var/config
        cap_add:
            - NET_ADMIN
            - SYS_MODULE
        ports:
            - 51820:51820/udp
            - 443:443 # Port for traefik because of the network_mode
            - 80:80 # Port for traefik because of the network_mode

    traefik:
        image: traefik:v3.3.3
        container_name: traefik
        restart: unless-stopped
        network_mode: service:gerbil # Ports appear on the gerbil service
        depends_on:
            pangolin:
                condition: service_healthy
        command:
            - --configFile=/etc/traefik/traefik_config.yml
        volumes:
            - ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration
            - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
networks:
    default:
        driver: bridge
        name: pangolin

traefik_config.yaml:

api:
    insecure: true
    dashboard: true

providers:
    http:
        endpoint: "http://pangolin:3001/api/v1/traefik-config"
        pollInterval: "5s"
    file:
        filename: "/etc/traefik/dynamic_config.yml"

experimental:
    plugins:
        badger:
            moduleName: "github.com/fosrl/badger"
            version: "v1.0.0-beta.3"

log:
    level: "INFO"
    format: "common"

certificatesResolvers:
    letsencrypt:
        acme:
            httpChallenge:
                entryPoint: web
            email: admin@example.com # REPLACE THIS WITH YOUR EMAIL
            storage: "/letsencrypt/acme.json"
            caServer: "https://acme-v02.api.letsencrypt.org/directory"

entryPoints:
    web:
        address: ":80"
    websecure:
        address: ":443"
        transport:
            respondingTimeouts:
                readTimeout: "30m"
        http:
            tls:
                certResolver: "letsencrypt"

serversTransport:
    insecureSkipVerify: true

dynamic_config.yml (hier die E-Mail-Adresse anpassen):

http:
    middlewares:
        redirect-to-https:
            redirectScheme:
                scheme: https

    routers:
        # HTTP to HTTPS redirect router
        main-app-router-redirect:
            rule: "Host(`proxy.example.com`)" # REPLACE THIS WITH YOUR DOMAIN
            service: next-service
            entryPoints:
                - web
            middlewares:
                - redirect-to-https

        # Next.js router (handles everything except API and WebSocket paths)
        next-router:
            rule: "Host(`proxy.example.com`) && !PathPrefix(`/api/v1`)" # REPLACE THIS WITH YOUR DOMAIN
            service: next-service
            entryPoints:
                - websecure
            tls:
                certResolver: letsencrypt

        # API router (handles /api/v1 paths)
        api-router:
            rule: "Host(`proxy.example.com`) && PathPrefix(`/api/v1`)" # REPLACE THIS WITH YOUR DOMAIN
            service: api-service
            entryPoints:
                - websecure
            tls:
                certResolver: letsencrypt

        # WebSocket router
        ws-router:
            rule: "Host(`proxy.example.com`)" # REPLACE THIS WITH YOUR DOMAIN
            service: api-service
            entryPoints:
                - websecure
            tls:
                certResolver: letsencrypt

    services:
        next-service:
            loadBalancer:
                servers:
                    - url: "http://pangolin:3002" # Next.js server

        api-service:
            loadBalancer:
                servers:
                    - url: "http://pangolin:3000" # API/WebSocket server

Start the Containers

Start the containers to generate the remaining files.

sudo docker compose -f pangolin/docker-compose.yml up -d

Once the config/config.yaml file is created, stop the containers and customize it.

sudo docker compose -f pangolin/docker-compose.yml down
sudo nano pangolin/config/config.yaml
app:
    dashboard_url: "https://example.com" # Auf deine Domain anpassen
    base_domain: "example.com" # Auf deine Domain anpassen
    log_level: "info" # debug, info, warn, error
    save_logs: false # true, false
    # log_failed_attempts: true # true, false

server:
    external_port: 3000
    internal_port: 3001
    next_port: 3002
    internal_hostname: "pangolin"
    session_cookie_name: "p_session_token"
    resource_access_token_param: "p_token"
    resource_session_request_param: "p_session_request"

traefik:
    cert_resolver: "letsencrypt"
    http_entrypoint: "web"
    https_entrypoint: "websecure"
    prefer_wildcard_cert: true

gerbil:
    start_port: 51820
    base_endpoint: "example.com" # Auf deine Domain anpassen
    use_subdomain: false
    block_size: 24
    site_block_size: 30
    subnet_group: 100.89.137.0/20

rate_limits:
    global:
        window_minutes: 1
        max_requests: 100

email:
    smtp_host: "host.hoster.net" # Auf deine E-Mail-Einstellungen anpassen
    smtp_port: 587 # Auf deine E-Mail-Einstellungen anpassen
    smtp_user: "no-reply@example.com" # Auf deine E-Mail-Einstellungen anpassen
    smtp_pass: "aaaaaaaaaaaaaaaaaa" # Auf deine E-Mail-Einstellungen anpassen
    no_reply: "no-reply@example.com" # Auf deine E-Mail-Einstellungen anpassen

users:
    server_admin:
        email: "admin@example.com" # Login Daten anpassen
        password: "Password123!" # Login Daten anpassen

flags:
    require_email_verification: true
    disable_signup_without_invite: true
    disable_user_create_org: true
    allow_raw_resources: true
    allow_base_domain_resources: true

Log In

Open your browser and navigate to the URL you specified in the config.yaml. Log in using the credentials you defined in the same file. Save the Newt configuration to establish a tunnel between your local server and the VPS.

Install Newt

With Newt, you create a secure tunnel between your local server and the VPS. This tunnel allows you to access your local services via the external server.

Install Newt on your local server:

mkdir newt
touch newt/docker-compose.yml
nano newt/docker-compose.yml
services:
    newt:
        image: fosrl/newt
        container_name: newt
        restart: unless-stopped
        environment:
            - PANGOLIN_ENDPOINT=https://example.com
            - NEWT_ID=2ix2t8xk22ubpfy
            - NEWT_SECRET=nnisrfsdfc7prqsp9ewo1dvtvci50j5uiqotez00dgap0ii2

Adjust the values for PANGOLIN_ENDPOINT, NEWT_ID, and NEWT_SECRET.

Start the container:

sudo docker compose -f newt/docker-compose.yml up -d

You should now see a successful connection in the Pangolin interface.

Create a Resource

Create a resource in the Pangolin interface to enable access to your local server.

Conclusion

With a reverse proxy and Pangolin, you can elegantly bypass the DS-Lite issue and securely access your local services from anywhere. While the setup might seem complex at first, the effort pays off: You gain full control over your data and independence from expensive cloud services.

Give it a try and share your experiences in the comments!


This website uses cookies. These are necessary for the functionality of the website. You can find more information in the privacy policy