Docker Compose
Deploy managed Pangolin manually using Docker Compose without the automated installer
This guide is for managed self-hosted Pangolin. If you are looking for self-hosted Community Edition Pangolin please see this Docker Compose guide.
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
│ └── traefik/
│ ├── traefik_config.yml (*)
└── docker-compose.yml (*)Files marked with (*) must be created manually. Volumes and other files are generated automatically by the services.
Configuration Files
config/config.yml: Main Pangolin configuration file
- Contains all Pangolin settings and options
- See Configuration Guide for details
config/traefik/traefik_config.yml: Traefik static configuration
- Global Traefik settings and entry points
Generated Files
config/db/db.sqlite: SQLite database file
- Created automatically on first startup
- Contains all Pangolin data and settings
config/key: Private key file
- Generated by Gerbil service
- Used for WireGuard tunnel encryption
Docker Files
docker-compose.yml: Service definitions
- Defines Pangolin, Gerbil, and Traefik services
- Network configuration and volume mounts
- Health checks and dependencies
Create configuration directory
mkdir -p config/traefik config/dbCreate configuration files
Create the main configuration files (see below):
docker-compose.yml(in project root)config/traefik/traefik_config.ymlconfig/config.yml
Update domain
Edit the configuration files to replace:
154.123.45.67with your actual domain OR public IP address of the node
Ensure your domain DNS is properly configured to point to your server's IP address if you choose DNS.
Starting the Stack
Start the services
sudo docker compose up -dMonitor startup
sudo docker compose logs -fVerify services
sudo docker compose psAll services should show "Up" status after a few minutes.
Docker Compose Configuration
Create docker-compose.yml in your project root:
services:
pangolin:
image: fosrl/pangolin:latest # https://github.com/fosrl/pangolin/releases
container_name: pangolin
restart: unless-stopped
volumes:
- ./config:/app/config
- pangolin-data:/var/certificates
- pangolin-data:/var/dynamic
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:3004
- --generateAndSaveKeyTo=/var/config/key
- --remoteConfig=http://pangolin:3001/api/v1/
volumes:
- ./config/:/var/config
cap_add:
- NET_ADMIN
- SYS_MODULE
ports:
- 51820:51820/udp
- 21820:21820/udp
- 443:8443
- 80:80
traefik:
image: traefik:v3.7
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
# Shared volume for certificates and dynamic config in file mode
- pangolin-data:/var/certificates:ro
- pangolin-data:/var/dynamic:ro
networks:
default:
driver: bridge
name: pangolin
volumes:
pangolin-data:Traefik Static Configuration
Create config/traefik/traefik_config.yml:
api:
insecure: true
dashboard: true
providers:
file:
directory: "/var/dynamic"
watch: true
experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
version: "v1.4.1"
log:
level: "INFO"
format: "common"
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
proxyProtocol:
trustedIPs:
- 0.0.0.0/0
- ::1/128
transport:
respondingTimeouts:
readTimeout: "30m"
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"Pangolin Configuration
gerbil:
start_port: 51820
base_endpoint: "154.123.45.67" # REPLACE WITH YOUR IP OR DOMAIN
managed:
id: "he4g78wevj25msf"
secret: "n7sd18twfko0q0vrb7wyclqzbvvnx1fqt7ezv8xewhdb9s7d"
