Merge pull request #106 from LaurenceJJones/docs/improve-self-host-install-guidance

enhance: manual self-host install guidance
This commit is contained in:
Milo Schwartz
2026-05-25 13:53:18 -07:00
committed by GitHub
2 changed files with 243 additions and 146 deletions

View File

@@ -7,146 +7,71 @@ import PangolinCloudTocCta from "/snippets/pangolin-cloud-toc-cta.mdx";
<PangolinCloudTocCta />
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:
<Tip>
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.
</Tip>
```
<Note>
`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`.
</Note>
## 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
```
<Info>
Files marked with `(*)` must be created manually. Volumes and other files are generated automatically by the services.
</Info>
The following files are created later by the running services or added only when you enable optional features:
<AccordionGroup>
<Accordion title="Configuration Files">
**`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
</Accordion>
- `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.
<Accordion title="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
**`config/letsencrypt/acme.json`**: SSL certificate storage
- Managed by Traefik
- Contains Let's Encrypt certificates
</Accordion>
## Create the Directories
<Accordion title="Docker Files">
**`docker-compose.yml`**: Service definitions
- Defines Pangolin, Gerbil, and Traefik services
- Network configuration and volume mounts
- Health checks and dependencies
</Accordion>
</AccordionGroup>
Create the project folders:
```bash
mkdir -p config/db config/letsencrypt config/traefik/logs
```
## Create the Configuration Files
<Steps>
<Step title="Create configuration directory">
```bash
mkdir -p config/traefik config/db config/letsencrypt config/logs
```
</Step>
<Step title="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`
</Step>
<Step title="Update domain and email">
Edit the configuration files to replace:
- `pangolin.example.com` with your actual domain
- `admin@example.com` with your email address
<Warning>
Ensure your domain DNS is properly configured to point to your server's IP address.
</Warning>
</Step>
</Steps>
## Starting the Stack
<Steps>
<Step title="Start the services">
```bash
sudo docker compose up -d
```
</Step>
<Step title="Monitor startup">
```bash
sudo docker compose logs -f
```
</Step>
<Step title="Verify services">
```bash
sudo docker compose ps
```
All services should show "Up" status after a few minutes.
</Step>
<Step title="Access the dashboard">
Navigate to `https://your-domain.com/auth/initial-setup` to complete the initial setup.
<Check>
The dashboard should load with SSL certificate automatically configured.
</Check>
</Step>
</Steps>
## Docker Compose Configuration
Create `docker-compose.yml` in your project root:
<Step title="Create docker-compose.yml">
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
<Note>
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.
</Note>
</Step>
Create `config/traefik/traefik_config.yml`:
<Step title="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
<Note>
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.
</Note>
</Step>
Create `config/traefik/dynamic_config.yml`:
<Step title="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
```
</Step>
## Pangolin Configuration
<Step title="Create config/config.yml">
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
```
<Warning>
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).
</Warning>
</Step>
</Steps>
### 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
<Steps>
<Step title="Start the services">
```bash
sudo docker compose up -d
```
</Step>
<Step title="Watch the logs">
```bash
sudo docker compose logs -f pangolin traefik gerbil
```
</Step>
<Step title="Verify the containers are healthy">
```bash
sudo docker compose ps
```
`pangolin`, `traefik`, and `gerbil` should all report as running after the first startup finishes.
</Step>
<Step title="Get the setup token from the Pangolin logs">
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.
</Step>
<Step title="Open the initial setup page">
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.
</Step>
</Steps>
## 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://<your-dashboard-domain>/auth/initial-setup` loads the setup page.
- `config/db/db.sqlite` exists after Pangolin starts.
- `config/key` exists after Gerbil starts.
<Tip>
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.
</Tip>
## 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).

View File

@@ -109,9 +109,20 @@ https://pangolin.example.com/auth/initial-setup
</Check>
</Step>
<Step title="Get the setup token from the Pangolin logs">
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.
</Step>
<Step title="Create 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)