This guide walks you through setting up Pangolin manually using Docker Compose without the automated installer. This approach gives you full control over the configuration and deployment process. This guide assumes you already have a Linux server with Docker and Docker Compose installed. If you don’t, please refer to the official Docker documentation for installation instructions. You must also have root access to the server.

Prerequisites

Checkout the quick install guide for more info regarding what is needed before you install Pangolin.

File Structure

Create the following directory structure for your Pangolin deployment:
.
├── config/
│   ├── config.yml (*)
│   ├── db/
│   │   └── db.sqlite
│   ├── key
│   ├── letsencrypt/
│   │   └── acme.json
│   ├── logs/
│   └── traefik/
│       ├── traefik_config.yml (*)
│       └── dynamic_config.yml (*)
└── docker-compose.yml (*)
Files marked with (*) must be created manually. Volumes and other files are generated automatically by the services.
1

Create configuration directory

mkdir -p config/traefik config/db config/letsencrypt config/logs
2

Create configuration files

Create the main configuration files (see below):
  • docker-compose.yml (in project root)
  • config/traefik/traefik_config.yml
  • config/traefik/dynamic_config.yml
  • config/config.yml
3

Update domain and email

Edit the configuration files to replace:
  • pangolin.example.com with your actual domain
  • admin@example.com with your email address
Ensure your domain DNS is properly configured to point to your server’s IP address.

Starting the Stack

1

Start the services

sudo docker compose up -d
2

Monitor startup

sudo docker compose logs -f
3

Verify services

sudo docker compose ps
All services should show “Up” status after a few minutes.
4

Access the dashboard

Navigate to https://your-domain.com/auth/initial-setup to complete the initial setup.
The dashboard should load with SSL certificate automatically configured.

Docker Compose Configuration

Create docker-compose.yml in your project root:
docker-compose.yml
services:
  pangolin:
    image: fosrl/pangolin:latest # https://github.com/fosrl/pangolin/releases
    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: 15

  gerbil:
    image: fosrl/gerbil:latest # https://github.com/fosrl/gerbil/releases
    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.4.0
    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 Static Configuration

Create config/traefik/traefik_config.yml:
config/traefik/traefik_config.yml
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: "latest"

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

certificatesResolvers:
  letsencrypt:
    acme:
      httpChallenge:
        entryPoint: web
      email: admin@example.com # REPLACE 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

Traefik Dynamic Configuration

Create config/traefik/dynamic_config.yml:
config/traefik/dynamic_config.yml
http:
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https

  routers:
    # HTTP to HTTPS redirect router
    main-app-router-redirect:
      rule: "Host(`pangolin.example.com`)" # REPLACE 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(`pangolin.example.com`) && !PathPrefix(`/api/v1`)" # REPLACE WITH YOUR DOMAIN
      service: next-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt

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

    # WebSocket router
    ws-router:
      rule: "Host(`pangolin.example.com`)" # REPLACE 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

Pangolin Configuration

Create config/config.yml with your Pangolin settings. See the configuration guide for detailed options and examples.