Files
docs-v2/self-host/manual/docker-compose.mdx
miloschwartz ece9cc9d23 minor changes
2026-05-25 13:52:39 -07:00

436 lines
12 KiB
Plaintext

---
title: "Docker Compose"
description: "Deploy Pangolin manually using Docker Compose without the automated installer"
---
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 assumes you already have a Linux server with Docker and Docker Compose installed, plus root or sudo access.
## Prerequisites
Review the [quick install guide](/self-host/quick-install) and [DNS & networking](/self-host/dns-and-networking) first. At minimum you need:
- 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
<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
.
├── docker-compose.yml
└── config/
├── config.yml
├── db/
├── letsencrypt/
└── traefik/
├── dynamic_config.yml
├── logs/
└── traefik_config.yml
```
The following files are created later by the running services or added only when you enable optional features:
- `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.
## Create the Directories
Create the project folders:
```bash
mkdir -p config/db config/letsencrypt config/traefik/logs
```
## Create the Configuration Files
<Steps>
<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
container_name: pangolin
restart: unless-stopped
volumes:
- ./config:/app/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: "10s"
timeout: "10s"
retries: 15
gerbil:
image: docker.io/fosrl/gerbil:latest
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: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
depends_on:
pangolin:
condition: service_healthy
command:
- --configFile=/etc/traefik/traefik_config.yml
volumes:
- ./config/traefik:/etc/traefik:ro
- ./config/letsencrypt:/letsencrypt
- ./config/traefik/logs:/var/log/traefik
networks:
default:
driver: bridge
name: pangolin
# enable_ipv6: true
```
<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>
<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:
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.4.0" # Check github.com/fosrl/badger for the latest release.
log:
level: "INFO"
format: "common"
maxSize: 100
maxBackups: 3
maxAge: 3
compress: true
certificatesResolvers:
letsencrypt:
acme:
httpChallenge:
entryPoint: web
email: "admin@example.com" # REPLACE
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
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"
encodedCharacters:
allowEncodedSlash: true
allowEncodedQuestionMark: true
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"
```
<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>
<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:
middlewares:
badger:
plugin:
badger:
disableForwardAuth: true
redirect-to-https:
redirectScheme:
scheme: https
routers:
main-app-router-redirect:
rule: "Host(`pangolin.example.com`)" # REPLACE
service: next-service
entryPoints:
- web
middlewares:
- redirect-to-https
- badger
next-router:
rule: "Host(`pangolin.example.com`) && !PathPrefix(`/api/v1`)" # REPLACE
service: next-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
api-router:
rule: "Host(`pangolin.example.com`) && PathPrefix(`/api/v1`)" # REPLACE
service: api-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
ws-router:
rule: "Host(`pangolin.example.com`)" # REPLACE
service: api-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
services:
next-service:
loadBalancer:
servers:
- url: "http://pangolin:3002"
api-service:
loadBalancer:
servers:
- url: "http://pangolin:3000"
tcp:
serversTransports:
pp-transport-v1:
proxyProtocol:
version: 1
pp-transport-v2:
proxyProtocol:
version: 2
```
</Step>
<Step title="Create config/config.yml">
This file contains Pangolin's application settings, dashboard domain, base domain, CORS origin, and server secret.
```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).