diff --git a/self-host/manual/docker-compose.mdx b/self-host/manual/docker-compose.mdx
index b84edc2..6248e20 100644
--- a/self-host/manual/docker-compose.mdx
+++ b/self-host/manual/docker-compose.mdx
@@ -7,146 +7,71 @@ import PangolinCloudTocCta from "/snippets/pangolin-cloud-toc-cta.mdx";
+This guide walks through a manual deployment using the same file layout the installer generates from `install/config/*` in the Pangolin source tree. Use it if you want the installer's defaults, but you want to create and maintain the files yourself.
-
-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](https://docs.docker.com/get-docker/) for installation instructions. You must also have root access to the server.
+This guide assumes you already have a Linux server with Docker and Docker Compose installed, plus root or sudo access.
## Prerequisites
-Checkout the [quick install guide](/self-host/quick-install) for more info regarding what is needed before you install Pangolin.
+Review the [quick install guide](/self-host/quick-install) and [DNS & networking](/self-host/dns-and-networking) first. At minimum you need:
-## File Structure
+- A public Linux server
+- A base domain such as `example.com`
+- A dashboard hostname such as `pangolin.example.com`
+- An email address for Let's Encrypt
+- TCP ports `80` and `443` open
+- UDP ports `51820` and `21820` open if you are using tunneling
-Create the following directory structure for your Pangolin deployment:
+
+If you do not want tunneling, see [Without Tunneling](/self-host/advanced/without-tunneling). In that mode you will skip the `gerbil` service and expose Traefik directly.
+
-```
+
+`base domain` is the parent domain you will attach resources to, such as `example.com`. `dashboard hostname` is the specific hostname for the Pangolin UI and API, such as `pangolin.example.com`.
+
+
+## File Layout
+
+Create the following project structure:
+
+```text
.
-├── config/
-│ ├── config.yml (*)
-│ ├── db/
-│ │ └── db.sqlite
-│ ├── key
-│ ├── letsencrypt/
-│ │ └── acme.json
-│ ├── logs/
-│ └── traefik/
-│ ├── traefik_config.yml (*)
-│ └── dynamic_config.yml (*)
-└── docker-compose.yml (*)
+├── docker-compose.yml
+└── config/
+ ├── config.yml
+ ├── db/
+ ├── letsencrypt/
+ └── traefik/
+ ├── dynamic_config.yml
+ ├── logs/
+ └── traefik_config.yml
```
-
-Files marked with `(*)` must be created manually. Volumes and other files are generated automatically by the services.
-
+The following files are created later by the running services or added only when you enable optional features:
-
-
- **`config/config.yml`**: Main Pangolin configuration file
- - Contains all Pangolin settings and options
- - See [Configuration Guide](/self-host/advanced/config-file) for details
-
- **`config/traefik/traefik_config.yml`**: Traefik static configuration
- - Global Traefik settings and entry points
- - SSL certificate resolver configuration
-
- **`config/traefik/dynamic_config.yml`**: Traefik dynamic configuration
- - HTTP routers and services for Pangolin
- - Load balancer and middleware configuration
-
+- `config/db/db.sqlite` is created by Pangolin on first startup.
+- `config/key` is created by Gerbil when tunneling is enabled.
+- `config/GeoLite2-Country.mmdb` is optional and only needed for [geo-blocking](/self-host/advanced/enable-geoblocking). It is not downloaded by the running services in a manual install; download it manually before enabling geo-blocking.
-
- **`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
-
- **`config/letsencrypt/acme.json`**: SSL certificate storage
- - Managed by Traefik
- - Contains Let's Encrypt certificates
-
+## Create the Directories
-
- **`docker-compose.yml`**: Service definitions
- - Defines Pangolin, Gerbil, and Traefik services
- - Network configuration and volume mounts
- - Health checks and dependencies
-
-
+Create the project folders:
+
+```bash
+mkdir -p config/db config/letsencrypt config/traefik/logs
+```
+
+## Create the Configuration Files
-
- ```bash
- mkdir -p config/traefik config/db config/letsencrypt config/logs
- ```
-
-
-
- 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`
-
-
-
- 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
-
-
-
- ```bash
- sudo docker compose up -d
- ```
-
-
-
- ```bash
- sudo docker compose logs -f
- ```
-
-
-
- ```bash
- sudo docker compose ps
- ```
-
- All services should show "Up" status after a few minutes.
-
-
-
- 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:
+
+ This file defines the Pangolin, Gerbil, and Traefik containers, their shared volumes, and the ports exposed on the host.
```yaml title="docker-compose.yml"
name: pangolin
services:
pangolin:
- image: docker.io/fosrl/pangolin:latest # https://github.com/fosrl/pangolin/releases
+ image: docker.io/fosrl/pangolin:latest
container_name: pangolin
restart: unless-stopped
volumes:
@@ -158,7 +83,7 @@ services:
retries: 15
gerbil:
- image: docker.io/fosrl/gerbil:latest # https://github.com/fosrl/gerbil/releases
+ image: docker.io/fosrl/gerbil:latest
container_name: gerbil
restart: unless-stopped
depends_on:
@@ -177,35 +102,38 @@ services:
- 51820:51820/udp
- 21820:21820/udp
- 443:443
+ # - 443:443/udp # Uncomment if you enable HTTP/3 in Traefik.
- 80:80
traefik:
image: docker.io/traefik:v3.6
container_name: traefik
restart: unless-stopped
-
- network_mode: service:gerbil # Ports appear on the gerbil service
-
+ network_mode: service:gerbil
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
- - ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs
+ - ./config/traefik:/etc/traefik:ro
+ - ./config/letsencrypt:/letsencrypt
+ - ./config/traefik/logs:/var/log/traefik
networks:
default:
driver: bridge
name: pangolin
- #enable_ipv6: true # activate if your system supports IPv6
+ # enable_ipv6: true
```
-## Traefik Static Configuration
+
+This is the installer's default community layout with Gerbil enabled. If you want to pin releases instead of using `latest`, replace the image tags with the versions you intend to run.
+
+
-Create `config/traefik/traefik_config.yml`:
+
+ This file configures Traefik's providers, Badger plugin, Let's Encrypt resolver, entry points, logs, and health check endpoint.
```yaml title="config/traefik/traefik_config.yml"
api:
@@ -223,7 +151,7 @@ experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
- version: "v1.3.1"
+ version: "v1.4.0" # Check github.com/fosrl/badger for the latest release.
log:
level: "INFO"
@@ -238,7 +166,7 @@ certificatesResolvers:
acme:
httpChallenge:
entryPoint: web
- email: "admin@example.com" # REPLACE WITH YOUR EMAIL
+ email: "admin@example.com" # REPLACE
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
@@ -250,6 +178,9 @@ entryPoints:
transport:
respondingTimeouts:
readTimeout: "30m"
+ # Uncomment to enable HTTP/3. You must also expose 443/udp in docker-compose.yml.
+ # http3:
+ # advertisedPort: 443
http:
tls:
certResolver: "letsencrypt"
@@ -264,9 +195,13 @@ ping:
entryPoint: "web"
```
-## Traefik Dynamic Configuration
+
+ Traefik stores Let's Encrypt certificates at `/letsencrypt/acme.json` inside the container. The Compose file mounts that path from `./config/letsencrypt`, so Traefik will create `config/letsencrypt/acme.json` when it needs certificate storage.
+
+
-Create `config/traefik/dynamic_config.yml`:
+
+ This file defines the routers, middleware, and services that send dashboard, API, and WebSocket traffic to Pangolin.
```yaml title="config/traefik/dynamic_config.yml"
http:
@@ -280,9 +215,8 @@ http:
scheme: https
routers:
- # HTTP to HTTPS redirect router
main-app-router-redirect:
- rule: "Host(`pangolin.example.com`)" # REPLACE WITH YOUR DOMAIN
+ rule: "Host(`pangolin.example.com`)" # REPLACE
service: next-service
entryPoints:
- web
@@ -290,9 +224,8 @@ http:
- redirect-to-https
- badger
- # Next.js router (handles everything except API and WebSocket paths)
next-router:
- rule: "Host(`pangolin.example.com`) && !PathPrefix(`/api/v1`)" # REPLACE WITH YOUR DOMAIN
+ rule: "Host(`pangolin.example.com`) && !PathPrefix(`/api/v1`)" # REPLACE
service: next-service
entryPoints:
- websecure
@@ -301,9 +234,8 @@ http:
tls:
certResolver: letsencrypt
- # API router (handles /api/v1 paths)
api-router:
- rule: "Host(`pangolin.example.com`) && PathPrefix(`/api/v1`)" # REPLACE WITH YOUR DOMAIN
+ rule: "Host(`pangolin.example.com`) && PathPrefix(`/api/v1`)" # REPLACE
service: api-service
entryPoints:
- websecure
@@ -312,9 +244,8 @@ http:
tls:
certResolver: letsencrypt
- # WebSocket router
ws-router:
- rule: "Host(`pangolin.example.com`)" # REPLACE WITH YOUR DOMAIN
+ rule: "Host(`pangolin.example.com`)" # REPLACE
service: api-service
entryPoints:
- websecure
@@ -327,12 +258,12 @@ http:
next-service:
loadBalancer:
servers:
- - url: "http://pangolin:3002" # Next.js server
+ - url: "http://pangolin:3002"
api-service:
loadBalancer:
servers:
- - url: "http://pangolin:3000" # API/WebSocket server
+ - url: "http://pangolin:3000"
tcp:
serversTransports:
@@ -343,7 +274,162 @@ tcp:
proxyProtocol:
version: 2
```
+
-## Pangolin Configuration
+
+ This file contains Pangolin's application settings, dashboard domain, base domain, CORS origin, and server secret.
-Create `config/config.yml` with your Pangolin settings. See the [configuration guide](/self-host/advanced/config-file) for detailed options and examples.
+```yaml title="config/config.yml"
+# To see all available options, please visit the docs:
+# https://docs.pangolin.net/
+
+gerbil:
+ start_port: 51820
+ base_endpoint: "pangolin.example.com" # REPLACE WITH YOUR DASHBOARD DOMAIN
+
+app:
+ dashboard_url: "https://pangolin.example.com" # REPLACE WITH YOUR DASHBOARD DOMAIN
+ log_level: "info"
+ telemetry:
+ anonymous_usage: true
+
+domains:
+ domain1:
+ base_domain: "example.com" # REPLACE WITH YOUR BASE DOMAIN
+
+server:
+ secret: "replace-with-a-long-random-secret" # REPLACE WITH SECURE SECRET
+ cors:
+ origins: ["https://pangolin.example.com"] # REPLACE WITH YOUR DASHBOARD DOMAIN
+ methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
+ allowed_headers: ["X-CSRF-Token", "Content-Type"]
+ credentials: false
+
+flags:
+ require_email_verification: false
+ disable_signup_without_invite: true
+ disable_user_create_org: false
+ allow_raw_resources: true
+```
+
+Replace these values before starting the stack:
+
+- `pangolin.example.com` with your dashboard hostname
+- `example.com` with your base domain
+- `replace-with-a-long-random-secret` with a strong random secret
+- `admin@example.com` in `traefik_config.yml` with your Let's Encrypt email
+
+Generate a secret with:
+
+```bash
+openssl rand -hex 32
+```
+
+
+Do not reuse a weak or short `server.secret`. If you need to rotate it later, use `pangctl rotate-server-secret`. See the [container CLI tool guide](/self-host/advanced/container-cli-tool#rotate-server-secret).
+
+
+
+
+
+### Optional Email Configuration
+
+If you want Pangolin to send email, add this block to `config/config.yml` and set `flags.require_email_verification` to `true`:
+
+```yaml title="config/config.yml"
+email:
+ smtp_host: "smtp.example.com"
+ smtp_port: 587
+ smtp_user: "smtp-user"
+ smtp_pass: "smtp-password"
+ no_reply: "noreply@example.com"
+```
+
+### Optional Geo-blocking Configuration
+
+If you want geo-blocking, download the MaxMind database and add this line under `server`:
+
+```yaml title="config/config.yml"
+server:
+ maxmind_db_path: "./config/GeoLite2-Country.mmdb"
+```
+
+See [Enable Geo-blocking](/self-host/advanced/enable-geoblocking) for the full process.
+
+## Start the Stack
+
+
+
+ ```bash
+ sudo docker compose up -d
+ ```
+
+
+
+ ```bash
+ sudo docker compose logs -f pangolin traefik gerbil
+ ```
+
+
+
+ ```bash
+ sudo docker compose ps
+ ```
+
+ `pangolin`, `traefik`, and `gerbil` should all report as running after the first startup finishes.
+
+
+
+ Check the Pangolin container logs:
+
+ ```bash
+ sudo docker compose logs pangolin
+ ```
+
+ Pangolin prints a setup token to stdout on first boot. Copy that token before continuing.
+
+
+
+ Visit:
+
+ ```text
+ https://pangolin.example.com/auth/initial-setup
+ ```
+
+ Replace the hostname with your real dashboard domain, then use the setup token from the Pangolin logs to register the first admin account.
+
+
+
+## Verify the Setup
+
+You should expect the following on a healthy first install:
+
+- `docker compose ps` shows `pangolin`, `traefik`, and `gerbil` as running.
+- `docker compose logs pangolin` includes the one-time setup token for the first admin account.
+- Visiting `https:///auth/initial-setup` loads the setup page.
+- `config/db/db.sqlite` exists after Pangolin starts.
+- `config/key` exists after Gerbil starts.
+
+
+The first Let's Encrypt certificate request can take a short while. If the page initially shows a certificate warning, wait a minute and refresh.
+
+
+## If Something Fails
+
+- If the setup page does not load, confirm your DNS record points to the server and ports `80` and `443` are reachable.
+- If you cannot complete first-time signup, check `sudo docker compose logs pangolin` and copy the setup token printed by Pangolin.
+- If certificates are not issued, confirm `admin@example.com` was replaced and that nothing else is already bound to ports `80` or `443`.
+- If `pangolin` never becomes healthy, inspect `sudo docker compose logs -f pangolin`.
+- If tunneling does not work, inspect `sudo docker compose logs -f gerbil` and confirm UDP ports `51820` and `21820` are open.
+- If Traefik serves the wrong host, re-check every `pangolin.example.com` replacement in both Traefik files and `config/config.yml`.
+
+## Without Tunneling
+
+If you do not want Gerbil:
+
+- Remove the `gerbil` service.
+- Remove `network_mode: service:gerbil` from `traefik`.
+- Add ports `80:80` and `443:443` directly to `traefik`.
+- Remove the `gerbil` block from `config/config.yml`.
+
+That mode is covered in more detail in [Without Tunneling](/self-host/advanced/without-tunneling).
diff --git a/self-host/quick-install.mdx b/self-host/quick-install.mdx
index 944aa86..5c8cc5a 100644
--- a/self-host/quick-install.mdx
+++ b/self-host/quick-install.mdx
@@ -109,9 +109,20 @@ https://pangolin.example.com/auth/initial-setup
+
+ Check the Pangolin container logs and copy the setup token printed to stdout:
+
+ ```bash
+ sudo docker compose logs pangolin
+ ```
+
+ You will need that token on the initial setup page to register the first admin account.
+
+
Complete the initial admin user setup:
+ - Paste the setup token from the Pangolin logs
- Enter your admin email address
- Set a strong password
- Verify your email (if email is configured)