port mintlify to fumadocs

This commit is contained in:
miloschwartz
2026-09-25 15:31:51 -04:00
parent dc54fb1017
commit 63199a588c
373 changed files with 13533 additions and 4577 deletions
@@ -0,0 +1,428 @@
---
title: "Docker Compose"
description: "Deploy Pangolin manually using Docker Compose without the automated installer"
---
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.7
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"
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).
@@ -0,0 +1,49 @@
---
title: "Choose an Installation Path"
description: "Choose the Kubernetes deployment workflow for Pangolin and Sites (Newt)."
---
Use this page to pick the right Kubernetes guide for your deployment workflow.
These guides assume you are already familiar with Kubernetes and the deployment tools listed below.
If you are new to Kubernetes, start with the [official Kubernetes learning resources](https://kubernetes.io/docs/tutorials/kubernetes-basics/) first. Then review the [Prerequisites](/self-host/manual/kubernetes/prerequisites) guide to check your cluster, tools, and setup.
## Installation paths
| Path | Use when | Start here |
| --- | --- | --- |
| Helm | You want the standard chart-based installation path for Pangolin or Sites (Newt). | [Helm Quick-Start](/self-host/manual/kubernetes/helm) |
| Kustomize | You want manifest overlays, for example for environment-specific configuration, patches, or rendered manifests that can be reviewed before applying. | [Kustomize Quick-Start](/self-host/manual/kubernetes/kustomize) |
| Argo CD | You already use Argo CD and want to deploy Pangolin or Sites (Newt) through a Kubernetes-native GitOps workflow. | [Argo CD Guide](/self-host/manual/kubernetes/gitops/argocd) |
| Flux | You already use Flux and want to manage Pangolin or Sites (Newt) through `HelmRelease` or `Kustomization` resources. | [Flux Guide](/self-host/manual/kubernetes/gitops/flux) |
| Helmfile | You want to manage multiple related Helm releases as one stack. | [Helmfile Guide](/self-host/manual/kubernetes/helmfile) |
## Recommended starting point
For most Kubernetes deployments, start with Helm. Use the GitOps guides only if Argo CD or Flux is already part of your deployment workflow.
Kustomize and Helmfile are useful when you need more control over manifests, overlays, or multiple coordinated releases.
## Next steps
<CardGroup cols={2}>
<Card title="Prerequisites" href="/self-host/manual/kubernetes/prerequisites" icon="list-check">
Review the required cluster, ingress, DNS, storage, and secret setup.
</Card>
<Card title="Helm Quick-Start" href="/self-host/manual/kubernetes/helm" icon="box">
Install Pangolin or Sites (Newt) with the standard chart-based workflow.
</Card>
<Card title="Kustomize Quick-Start" href="/self-host/manual/kubernetes/kustomize" icon="layer-group">
Use overlays and patches for manifest-based deployments.
</Card>
<Card title="Argo CD Guide" href="/self-host/manual/kubernetes/gitops/argocd" icon="code-branch">
Deploy Pangolin or Sites (Newt) with Argo CD.
</Card>
<Card title="Flux Guide" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch">
Deploy Pangolin or Sites (Newt) with Flux.
</Card>
<Card title="Helmfile Guide" href="/self-host/manual/kubernetes/helmfile" icon="boxes-stacked">
Manage multiple Helm releases together.
</Card>
</CardGroup>
@@ -0,0 +1,419 @@
---
title: "Argo CD"
description: "Deploy Pangolin and Newt using Argo CD for Git-driven GitOps reconciliation."
---
Argo CD is a declarative GitOps tool that continuously syncs your cluster state to your Git repository. This guide covers installing Pangolin and Newt using Argo CD.
## Install Pangolin with Argo CD using Helm
### Step 1: Create Pangolin namespace
```bash
kubectl create namespace pangolin
```
### Step 2: Create Application
Create an Argo CD Application resource that tells Argo CD to deploy Pangolin using the Helm chart:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: pangolin
namespace: argocd
spec:
project: default
source:
repoURL: https://charts.fossorial.io
chart: pangolin
targetRevision: 0.1.0-alpha.0 # or use ~0.1.0 for range
helm:
values: |
deployment:
type: controller
mode: multi
database:
mode: cloudnativepg
pangolin:
config:
app:
dashboard_url: https://pangolin.example.com
domains:
domain1:
base_domain: example.com
gerbil:
base_endpoint: vpn.example.com
ingress:
enabled: true
className: traefik
hosts:
- host: pangolin.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: pangolin-tls
hosts:
- pangolin.example.com
destination:
server: https://kubernetes.default.svc
namespace: pangolin
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
```
Apply the Application:
```bash
kubectl apply -f pangolin-app.yaml
```
### Step 3: Monitor in Argo CD
In the Argo CD UI, you should see the `pangolin` application. Argo CD will:
1. Fetch the Helm chart from `https://charts.fossorial.io`
2. Render the chart with your inline `values`
3. Create all resources in the `pangolin` namespace
4. Continuously monitor for drift
### Step 4: Verify deployment
```bash
# Check Argo CD status
kubectl describe app -n argocd pangolin
# Check pod status
kubectl get pods -n pangolin
```
## Install Newt with Argo CD using Helm
### Step 1: Create Newt auth secret
```bash
kubectl create secret generic newt-auth \
-n pangolin \
--from-literal=PANGOLIN_ENDPOINT=https://pangolin.example.com \
--from-literal=NEWT_ID=<your-newt-id> \
--from-literal=NEWT_SECRET=<your-newt-secret>
```
### Step 2: Create Newt Application
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: newt
namespace: argocd
spec:
project: default
source:
repoURL: https://charts.fossorial.io
chart: newt
targetRevision: 1.4.0
helm:
values: |
newtInstances:
- name: main-tunnel
enabled: true
auth:
existingSecretName: newt-auth
destination:
server: https://kubernetes.default.svc
namespace: pangolin
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
```
Apply:
```bash
kubectl apply -f newt-app.yaml
```
## Using Argo CD with Git repository
Instead of inline values, you can store configuration in Git and have Argo CD deploy from there:
### Repository structure
```
infrastructure/
├── apps/
│ ├── pangolin/
│ │ ├── values-base.yaml
│ │ ├── values-prod.yaml
│ │ └── app.yaml (Argo CD Application CRD)
│ └── newt/
│ ├── values.yaml
│ └── app.yaml
└── clusters/
└── production/
├── pangolin.yaml (reference to app)
└── newt.yaml
```
### Git-based Application
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: pangolin
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/my-org/infrastructure
path: apps/pangolin
targetRevision: main
helm:
valuesObject:
deployment:
type: controller
mode: multi
releaseName: pangolin
destination:
server: https://kubernetes.default.svc
namespace: pangolin
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
```
Argo CD will watch the Git repository and auto-sync on changes to `apps/pangolin`.
## Using Argo CD with Kustomize
Deploy Pangolin using Kustomize overlays:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: pangolin
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/my-org/infrastructure
path: overlays/production
targetRevision: main
destination:
server: https://kubernetes.default.svc
namespace: pangolin
syncPolicy:
syncOptions:
- CreateNamespace=true
automated:
prune: true
selfHeal: true
```
## Sync policies
### Automated sync
**prune: true**: Deletes resources in cluster that are no longer in Git
**selfHeal: true**: Resyncs if cluster drifts from Git (e.g., manual `kubectl apply`)
```yaml
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false # prevent accidental deletion of all resources
```
### Manual sync
Sync only when you explicitly trigger it:
```yaml
syncPolicy:
syncOptions:
- CreateNamespace=true
```
Manually sync:
```bash
argocd app sync pangolin
# or use UI
```
## Advanced: ApplicationSet for multi-environment
Deploy Pangolin and Newt across multiple clusters or environments:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: pangolin-multienv
namespace: argocd
spec:
generators:
- list:
elements:
- cluster: production
env: prod
- cluster: staging
env: staging
template:
metadata:
name: pangolin-{{ .cluster }}
spec:
project: default
source:
repoURL: https://github.com/my-org/infrastructure
path: clusters/{{ .cluster }}/pangolin
targetRevision: main
destination:
name: '{{ .cluster }}'
namespace: pangolin
syncPolicy:
automated:
prune: true
selfHeal: true
```
## OCI Helm sources (if available)
If the Helm chart is available in an OCI registry:
```yaml
source:
repoURL: oci://registry.example.com/fossorial
chart: pangolin
targetRevision: 0.1.0-alpha.0
helm:
values: |
# ... values ...
```
OCI chart references work the same as traditional Helm repository references in Argo CD.
## Troubleshooting Argo CD deployments
### Check Application status
```bash
kubectl describe app -n argocd pangolin
kubectl get app -n argocd pangolin -o yaml
```
### Check sync status
```bash
argocd app get pangolin
argocd app logs pangolin
```
### Manual sync
```bash
argocd app sync pangolin --force
```
### Refresh from repository
```bash
argocd app diff pangolin
```
### Delete Application
```bash
kubectl delete app -n argocd pangolin
```
## Common patterns
### Different values per environment
Use multiple Applications:
```yaml
# production/pangolin-app.yaml
spec:
source:
helm:
values: |
resources:
limits:
cpu: 2000m
memory: 2Gi
replicas: 3
# staging/pangolin-app.yaml
spec:
source:
helm:
values: |
resources:
limits:
cpu: 500m
memory: 512Mi
replicas: 1
```
### Secrets with sealed-secrets
Use sealed-secrets to safely store secrets in Git:
```yaml
# In Git
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: newt-auth
namespace: pangolin
spec:
encryptedData:
PANGOLIN_ENDPOINT: AgC4F5qd...
NEWT_ID: AgB9l2pK...
NEWT_SECRET: AgDq3jX...
```
Argo CD applies the sealed secret; the cluster decrypts it.
## Next steps
<CardGroup cols={2}>
<Card title="GitOps Overview" href="/self-host/manual/kubernetes/gitops/overview" icon="code-branch" />
<Card title="Flux" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch" />
<Card title="Pangolin Configuration" href="/self-host/manual/kubernetes/pangolin/configuration" icon="sliders" />
<Card title="Troubleshooting" href="/self-host/manual/kubernetes/pangolin/troubleshooting" icon="circle-question" />
</CardGroup>
@@ -0,0 +1,512 @@
---
title: "Flux"
description: "Deploy Pangolin and Newt using Flux for Git-driven GitOps reconciliation."
---
Flux is a declarative GitOps tool that uses Kubernetes-native Custom Resources to manage deployments. This guide covers installing Pangolin and Newt using Flux.
## Flux prerequisites
- Kubernetes 1.25+
- `flux` CLI installed: [Flux install guide](https://fluxcd.io/flux/installation/)
- Git repository for configuration (optional, can use built-in sources)
- GitHub, GitLab, or other Git provider account (optional)
Install Flux CLI:
```bash
# macOS/Linux with brew
brew install flux
# or curl
curl -s https://fluxcd.io/install.sh | sudo bash
# Verify
flux --version
```
## Install Flux on your cluster
### Option 1: Bootstrap Flux from GitHub
Flux `bootstrap` automatically installs Flux and configures Git sync:
```bash
flux bootstrap github \
--owner=my-org \
--repo=infrastructure \
--personal \
--path=clusters/production
```
This creates the Git repository structure and installs Flux components.
### Option 2: Manual Flux installation
```bash
# Create flux-system namespace and install Flux
flux install --namespace=flux-system --network-policy=true
```
## Install Pangolin with Flux using HelmRelease
### Step 1: Create HelmRepository
Define the Fossorial Helm chart repository:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: fossorial
namespace: flux-system
spec:
interval: 5m
url: https://charts.fossorial.io
```
Apply:
```bash
kubectl apply -f helmrepo.yaml
# Verify
kubectl get helmrepo -n flux-system
```
### Step 2: Create Pangolin HelmRelease
```yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: pangolin
namespace: pangolin
spec:
interval: 10m
chart:
spec:
chart: pangolin
version: 0.1.0-alpha.0 # or use ~0.1.0 for auto-upgrades
sourceRef:
kind: HelmRepository
name: fossorial
namespace: flux-system
install:
crds: Create
upgrade:
crds: CreateReplace
values:
deployment:
type: controller
mode: multi
database:
mode: cloudnativepg
pangolin:
config:
app:
dashboard_url: https://pangolin.example.com
domains:
domain1:
base_domain: example.com
gerbil:
base_endpoint: vpn.example.com
ingress:
enabled: true
className: traefik
hosts:
- host: pangolin.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: pangolin-tls
hosts:
- pangolin.example.com
```
Create namespace:
```bash
kubectl create namespace pangolin
```
Apply:
```bash
kubectl apply -f pangolin-helmrelease.yaml
```
### Step 3: Monitor reconciliation
```bash
# Check HelmRelease status
kubectl get helmrelease -n pangolin
# Watch live
kubectl get helmrelease -n pangolin -w
# Describe for details
kubectl describe helmrelease pangolin -n pangolin
# Check Flux logs
flux logs --all-namespaces --follow
```
## Install Newt with Flux using HelmRelease
### Step 1: Create Newt auth secret
```bash
kubectl create secret generic newt-auth \
-n pangolin \
--from-literal=PANGOLIN_ENDPOINT=https://pangolin.example.com \
--from-literal=NEWT_ID=<your-newt-id> \
--from-literal=NEWT_SECRET=<your-newt-secret>
```
### Step 2: Create Newt HelmRelease
```yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: newt
namespace: pangolin
spec:
interval: 10m
chart:
spec:
chart: newt
version: 1.4.0
sourceRef:
kind: HelmRepository
name: fossorial
namespace: flux-system
values:
newtInstances:
- name: main-tunnel
enabled: true
auth:
existingSecretName: newt-auth
```
Apply:
```bash
kubectl apply -f newt-helmrelease.yaml
```
### Step 3: Verify
```bash
kubectl get helmrelease -n pangolin
kubectl describe helmrelease newt -n pangolin
```
## Using Flux with Git repository (GitOps)
Store Flux configuration in Git and have Flux automatically reconcile changes:
### Repository structure
```
infrastructure/
├── clusters/
│ └── production/
│ ├── flux-system/
│ │ └── gotk-components.yaml (auto-generated)
│ ├── pangolin/
│ │ ├── helmrepo.yaml
│ │ ├── pangolin-helmrelease.yaml
│ │ └── newt-helmrelease.yaml
│ └── kustomization.yaml
└── apps/
├── pangolin/
│ └── values.yaml
└── newt/
└── values.yaml
```
### GitRepository for configuration
```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: infrastructure
namespace: flux-system
spec:
interval: 1m
url: https://github.com/my-org/infrastructure
ref:
branch: main
```
### Kustomization for syncing
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: production
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: infrastructure
path: ./clusters/production
prune: true
wait: true
```
Flux watches `clusters/production` in Git and auto-applies all resources.
## Using Flux with Kustomize overlays
Manage environment-specific overlays with Flux:
### Repository structure
```
overlays/
├── dev/
│ ├── kustomization.yaml
│ └── pangolin-patch.yaml
├── staging/
│ └── kustomization.yaml
└── prod/
├── kustomization.yaml
└── pangolin-patch.yaml
```
### Kustomization resource
```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: pangolin-prod
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: infrastructure
path: ./overlays/prod
prune: true
wait: true
```
Flux builds and applies the Kustomize overlay automatically.
## Using Flux with OCI Helm charts
If Helm charts are available in an OCI registry:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: OCIRepository
metadata:
name: fossorial-oci
namespace: flux-system
spec:
interval: 5m
url: oci://registry.example.com/fossorial
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: pangolin
namespace: pangolin
spec:
interval: 10m
chart:
spec:
chart: pangolin
version: 0.1.0-alpha.0
sourceRef:
kind: OCIRepository
name: fossorial-oci
namespace: flux-system
values:
# ... values ...
```
## Advanced: Dependency ordering
Order HelmReleases to install dependencies first:
```yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: cert-manager
namespace: cert-manager
spec:
interval: 10m
chart:
spec:
chart: cert-manager
# ...
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: pangolin
namespace: pangolin
spec:
interval: 10m
dependsOn:
- name: cert-manager
namespace: cert-manager
chart:
spec:
chart: pangolin
# ...
```
Flux ensures `cert-manager` reconciles before `pangolin`.
## Advanced: valuesFrom ConfigMap/Secret
Store values in ConfigMaps or Secrets, referenced from HelmRelease:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: pangolin-values
namespace: pangolin
data:
values.yaml: |
deployment:
type: controller
mode: multi
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: pangolin
namespace: pangolin
spec:
interval: 10m
chart:
spec:
chart: pangolin
# ...
valuesFrom:
- kind: ConfigMap
name: pangolin-values
```
Flux extracts values from the ConfigMap and applies them to the HelmRelease.
## Troubleshooting Flux
### Check Flux components
```bash
kubectl get deployments -n flux-system
flux check --all-namespaces
```
### Check HelmRelease status
```bash
kubectl get helmrelease -n pangolin
kubectl describe helmrelease pangolin -n pangolin
kubectl get helmrelease pangolin -n pangolin -o yaml
```
### View reconciliation logs
```bash
flux logs --all-namespaces --follow
# Specific resource
kubectl logs -n pangolin deployment/helm-operator -f
```
### Manual reconciliation
```bash
flux reconcile helmrelease pangolin -n pangolin
flux reconcile kustomization production -n flux-system
```
### Suspend reconciliation
```bash
flux suspend helmrelease pangolin -n pangolin
```
### Resume reconciliation
```bash
flux resume helmrelease pangolin -n pangolin
```
## Multi-environment example
### Bootstrap multiple clusters
```bash
# Production cluster
flux bootstrap github \
--owner=my-org \
--repo=infrastructure \
--personal \
--path=clusters/production
# Staging cluster (from different checkout)
flux bootstrap github \
--owner=my-org \
--repo=infrastructure \
--personal \
--path=clusters/staging
```
Each cluster reconciles its own `clusters/*/` directory.
### Repository structure
```
clusters/
├── production/
│ ├── kustomization.yaml
│ └── pangolin/
│ ├── helmrepo.yaml
│ └── helmrelease.yaml (prod values)
├── staging/
│ ├── kustomization.yaml
│ └── pangolin/
│ ├── helmrepo.yaml
│ └── helmrelease.yaml (staging values)
└── dev/
├── kustomization.yaml
└── pangolin/
└── helmrelease.yaml (dev values)
```
Each environment's HelmRelease uses environment-specific values.
## Next steps
<CardGroup cols={2}>
<Card title="GitOps Overview" href="/self-host/manual/kubernetes/gitops/overview" icon="code-branch" />
<Card title="Argo CD" href="/self-host/manual/kubernetes/gitops/argocd" icon="code-branch" />
<Card title="Pangolin Configuration" href="/self-host/manual/kubernetes/pangolin/configuration" icon="sliders" />
<Card title="Troubleshooting" href="/self-host/manual/kubernetes/pangolin/troubleshooting" icon="circle-question" />
</CardGroup>
@@ -0,0 +1,72 @@
---
title: "GitOps Overview"
description: "Deploy Pangolin and Sites (Newt) with GitOps workflows such as Argo CD or Flux."
---
Use GitOps when Pangolin and Sites (Newt) should be reconciled from Git instead of being installed manually from a local shell.
Can be used together with Blueprints — see [Blueprint config reference](/self-host/advanced/config-file) for details.
These guides assume you already use, or plan to use, a GitOps controller such as Argo CD or Flux.
General GitOps concepts such as reconciliation, desired state, and Git-driven workflows are outside the scope of this documentation. Refer to your GitOps controller's documentation for those concepts.
## Supported GitOps paths
<CardGroup cols={2}>
<Card title="Argo CD Guide" href="/self-host/manual/kubernetes/gitops/argocd" icon="code-branch">
Deploy Pangolin or Sites (Newt) with Argo CD Applications.
</Card>
<Card title="Flux Guide" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch">
Deploy Pangolin or Sites (Newt) with Flux HelmRelease or Kustomization resources.
</Card>
</CardGroup>
## What GitOps manages
A GitOps workflow can reconcile the same deployment inputs used by the other Kubernetes guides:
| Input | Used for |
| --- | --- |
| Helm chart values | Configure Pangolin, controller mode, database mode, ingress, Sites, and related components. |
| Kustomize overlays | Patch or compose rendered manifests for environment-specific deployments. |
| Kubernetes Secrets | Provide credentials, TLS material, database connection details, or Site connector credentials. |
| Custom resources | Manage Argo CD Applications, Flux HelmReleases, Flux Kustomizations, or related controller resources. |
## Recommended layout
Keep the Pangolin and Site configuration close to the cluster or environment that owns it.
```text
infrastructure/
├── clusters/
│ ├── production/
│ │ ├── pangolin/
│ │ └── sites/
│ ├── staging/
│ │ ├── pangolin/
│ │ └── sites/
│ └── dev/
│ ├── pangolin/
│ └── sites/
└── shared/
├── pangolin/
└── sites/
```
Use environment-specific directories for values, patches, and secrets that differ between clusters. Use shared directories only for reusable configuration that should stay the same across environments.
## Next steps
<CardGroup cols={2}>
<Card title="Argo CD Guide" href="/self-host/manual/kubernetes/gitops/argocd" icon="code-branch">
Create Argo CD Applications for Pangolin and Sites (Newt).
</Card>
<Card title="Flux Guide" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch">
Create Flux sources, HelmReleases, or Kustomizations for Pangolin and Sites (Newt).
</Card>
<Card title="Choose an Installation Path" href="/self-host/manual/kubernetes/choose-method" icon="route">
Compare the supported Kubernetes deployment paths.
</Card>
<Card title="Prerequisites" href="/self-host/manual/kubernetes/prerequisites" icon="list-check">
Review cluster, networking, storage, RBAC, and resource requirements.
</Card>
</CardGroup>
@@ -0,0 +1,372 @@
---
title: "Helm"
description: "Kubernetes installation using Helm charts for Pangolin and Newt."
---
Helm is the recommended method for standard Kubernetes installations of Pangolin and Newt.
Use Helm when you want a chart-based workflow for installing, upgrading, rolling back, and removing releases from your cluster.
## Helm repository setup
Add the Fossorial Helm chart repository:
```bash
helm repo add fossorial https://charts.fossorial.io
helm repo update fossorial
```
Search for available charts:
```bash
helm search repo fossorial
```
The classic Helm repository flow is the default path for most installations:
```bash
helm install my-newt fossorial/newt
helm install my-pangolin fossorial/pangolin
```
## Installation overview
A typical Helm installation flow looks like this:
<Steps>
<Step title="Create namespace and labels">
Create the namespace manually and apply required labels or annotations.
</Step>
<Step title="Prepare values files">
Create a `values.yaml` file for each release (`values-pangolin.yaml`, `values-newt.yaml`).
</Step>
<Step title="Install with Helm">
Install with `helm upgrade --install` to support first install and future updates with the same command.
</Step>
<Step title="Verify release and resources">
Confirm Helm release status and Kubernetes resources after deployment.
</Step>
</Steps>
<Info>
It is recommended to create the namespace explicitly before installation. This allows you to apply Pod Security Admission labels, policy labels, annotations, or other cluster-specific metadata before the chart creates workloads.
</Info>
For detailed installation steps, see:
* [Pangolin Helm Quick-Start](/self-host/manual/kubernetes/pangolin/helm) — Install Pangolin
## Install command patterns
```bash tab="Classic Helm repository"
helm upgrade --install pangolin fossorial/pangolin \
--namespace pangolin \
--values values-pangolin.yaml
helm upgrade --install newt fossorial/newt \
--namespace pangolin \
--values values-newt.yaml
```
```bash tab="OCI (GHCR)"
helm upgrade --install pangolin oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0 \
--namespace pangolin \
--values values-pangolin.yaml
helm upgrade --install newt oci://ghcr.io/fosrl/helm-charts/newt \
--version 1.4.0 \
--namespace pangolin \
--values values-newt.yaml
```
## Namespace preparation
Create the namespace before installing the chart:
```bash
kubectl create namespace pangolin
```
If your cluster uses Pod Security Admission or namespace-based policies, apply the required labels before installation.
Example:
```bash
kubectl label namespace pangolin \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
```
<Warning>
Pangolin deployments that include Gerbil require permissions that are not compatible with a restricted namespace profile, because Gerbil manages WireGuard and requires capabilities such as `NET_ADMIN`.
</Warning>
For more details, see [Prerequisites](/self-host/manual/kubernetes/prerequisites).
## Install with a values file
Both charts use values files for configuration.
Pangolin example:
```bash
helm upgrade --install pangolin fossorial/pangolin \
--namespace pangolin \
--values values-pangolin.yaml
```
Newt example:
```bash
helm upgrade --install newt fossorial/newt \
--namespace pangolin \
--values values-newt.yaml
```
Using `helm upgrade --install` keeps the command usable for both the first installation and later configuration changes.
<Note>
Do not use `--create-namespace` if you need custom namespace labels or annotations. Create the namespace first and then run Helm against that namespace.
</Note>
## Values and configuration
Keep reusable configuration in a values file:
```bash
helm upgrade --install pangolin fossorial/pangolin \
--namespace pangolin \
--values values-pangolin.yaml
```
Use `--set` only for small tests or temporary overrides:
```bash
helm upgrade --install pangolin fossorial/pangolin \
--namespace pangolin \
--set example.key=value
```
Common value sources:
* `values-pangolin.yaml` for Pangolin.
* `values-newt.yaml` for Newt.
* Kubernetes Secrets for credentials.
* Existing cluster resources such as TLS secrets, StorageClasses, or ingress controllers.
Full configuration options are documented here:
* [Pangolin Configuration](/self-host/manual/kubernetes/pangolin/configuration)
## Artifact Hub and chart discovery
The Fossorial charts can be installed from the Fossorial Helm repository:
```bash
helm repo add fossorial https://charts.fossorial.io
helm repo update fossorial
helm search repo fossorial
```
Artifact Hub can also be used to discover published chart metadata, available versions, install commands, and repository information.
<Note>
Always verify the chart name, chart version, and repository URL before copying install commands into production.
</Note>
## OCI-based charts
OCI is not a separate installation method. It only changes where Helm pulls the chart from.
For Pangolin and Newt, OCI chart publishing is available in GHCR:
* Newt: `oci://ghcr.io/fosrl/helm-charts/newt`
* Pangolin: `oci://ghcr.io/fosrl/helm-charts/pangolin`
You still use Helm in the same way: choose a chart, select a version, provide values, and install the release.
### Pull OCI charts
Newt example:
```bash
helm pull oci://ghcr.io/fosrl/helm-charts/newt \
--version 1.4.0
```
Pangolin example:
```bash
helm pull oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0
```
### Install from OCI
Newt example:
```bash
helm upgrade --install newt oci://ghcr.io/fosrl/helm-charts/newt \
--version 1.4.0 \
--namespace pangolin \
--values values-newt.yaml
```
Pangolin example:
```bash
helm upgrade --install pangolin oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0 \
--namespace pangolin \
--values values-pangolin.yaml
```
<Info>
Use the classic Helm repository when you want the normal `helm repo add` and `helm search repo` workflow. Use OCI when you want to pull charts directly from GHCR or when your deployment tooling expects OCI chart references.
</Info>
## Upgrade and maintenance
### Update the classic Helm repository
```bash
helm repo update fossorial
```
This step is only needed when using the classic Helm repository. OCI installs pull the chart by OCI reference and version.
### Upgrade Pangolin
Classic Helm repository:
```bash
helm upgrade pangolin fossorial/pangolin \
--namespace pangolin \
--values values-pangolin.yaml
```
OCI:
```bash
helm upgrade pangolin oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0 \
--namespace pangolin \
--values values-pangolin.yaml
```
### Upgrade Newt
Classic Helm repository:
```bash
helm upgrade newt fossorial/newt \
--namespace pangolin \
--values values-newt.yaml
```
OCI:
```bash
helm upgrade newt oci://ghcr.io/fosrl/helm-charts/newt \
--version 1.4.0 \
--namespace pangolin \
--values values-newt.yaml
```
### Check release status
```bash
helm status pangolin --namespace pangolin
helm history pangolin --namespace pangolin
```
```bash
helm status newt --namespace pangolin
helm history newt --namespace pangolin
```
### View rendered manifests
```bash
helm get manifest pangolin --namespace pangolin
```
```bash
helm get manifest newt --namespace pangolin
```
### View applied values
```bash
helm get values pangolin --namespace pangolin
```
```bash
helm get values newt --namespace pangolin
```
### Roll back a release
```bash
helm rollback pangolin <revision> --namespace pangolin
```
```bash
helm rollback newt <revision> --namespace pangolin
```
### Uninstall a release
```bash
helm uninstall pangolin --namespace pangolin
```
```bash
helm uninstall newt --namespace pangolin
```
<Warning>
Uninstalling a Helm release does not always remove persistent volumes, externally managed secrets, DNS records, certificates, or cloud load balancers. Review the namespace and related cluster resources before deleting data.
</Warning>
## Troubleshooting
* [Pangolin Troubleshooting](/self-host/manual/kubernetes/pangolin/troubleshooting)
Useful Helm commands:
```bash
helm list --all-namespaces
helm status <release-name> --namespace <namespace>
helm history <release-name> --namespace <namespace>
helm get values <release-name> --namespace <namespace>
helm get manifest <release-name> --namespace <namespace>
```
Useful Kubernetes commands:
```bash
kubectl get pods -n pangolin
kubectl get events -n pangolin --sort-by=.lastTimestamp
kubectl describe pod <pod-name> -n pangolin
kubectl logs <pod-name> -n pangolin
```
## Next steps
<CardGroup cols={2}>
<Card title="Pangolin Helm Install" href="/self-host/manual/kubernetes/pangolin/helm" icon="server">
Install Pangolin with the Helm chart.
</Card>
<Card title="Pangolin Configuration" href="/self-host/manual/kubernetes/pangolin/configuration" icon="sliders">
Configure Pangolin chart values for your cluster.
</Card>
<Card title="Argo CD" href="/self-host/manual/kubernetes/gitops/argocd" icon="code-branch">
Deploy the charts with Argo CD.
</Card>
<Card title="Flux" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch">
Deploy the charts with Flux.
</Card>
</CardGroup>
@@ -0,0 +1,381 @@
---
title: "Helmfile"
description: "Advanced Kubernetes installation using Helmfile for multi-release orchestration."
---
Helmfile is a declarative way to manage multiple Helm releases in a single workflow. Use Helmfile when you need to install Pangolin and/or Newt alongside other Kubernetes components or manage multiple releases together.
## When to use Helmfile
Use Helmfile if you want to:
- **Orchestrate multiple Helm releases** in a single file (Pangolin + Newt + dependencies).
- **Manage dependencies** between releases (e.g., install cert-manager before Pangolin).
- **Keep release definitions** in version control and synchronized.
- **Avoid repeated `helm install` commands** for complex multi-release setups.
**Not using Helmfile?** If you're installing only Pangolin or only Newt without additional services, [Helm quick-start](/self-host/manual/kubernetes/helm) is simpler.
## Helm vs. Helmfile
| Aspect | Helm | Helmfile |
| --- | --- | --- |
| **Purpose** | Install/manage a single Helm chart release | Orchestrate multiple Helm chart releases |
| **Command** | `helm install`, `helm upgrade` | `helmfile sync`, `helmfile apply` |
| **Use case** | Quick install, single app | Multi-release, dependencies, fleet management |
| **Complexity** | Low | Medium |
## Helmfile prerequisites
- Helm 3.10+
- `helmfile` CLI installed: [Helmfile GitHub](https://github.com/helmfile/helmfile)
- Basic knowledge of Helm values and YAML
Install helmfile:
```bash
# macOS/Linux with brew
brew install helmfile
# or download from releases
wget https://github.com/helmfile/helmfile/releases/download/v<version>/helmfile_<os>_<arch>
chmod +x helmfile
sudo mv helmfile /usr/local/bin/
```
Verify:
```bash
helmfile --version
```
## Basic Helmfile structure
A Helmfile is a YAML file (typically named `helmfile.yaml`) that declares multiple releases:
```yaml
# helmfile.yaml
releases:
- name: cert-manager
namespace: cert-manager
createNamespace: true
chart: jetstack/cert-manager
version: v1.14.0
- name: pangolin
namespace: pangolin
createNamespace: true
chart: fossorial/pangolin
version: 0.1.0-alpha.0
values:
- pangolin-values.yaml
- name: newt
namespace: pangolin
chart: fossorial/newt
version: 1.4.0
values:
- newt-values.yaml
dependsOn:
- pangolin
```
## Helmfile with Pangolin and Newt
### 1. Add Helm repositories
```bash
helm repo add jetstack https://charts.jetstack.io
helm repo add fossorial https://charts.fossorial.io
helm repo update
```
### 2. Create Helmfile
Create `helmfile.yaml`:
```yaml
helmDefaults:
atomic: true
cleanupOnFail: true
wait: true
timeout: 600
recreatePods: true
force: false
repositories:
- name: jetstack
url: https://charts.jetstack.io
- name: fossorial
url: https://charts.fossorial.io
releases:
- name: cert-manager
namespace: cert-manager
createNamespace: true
chart: jetstack/cert-manager
version: v1.14.0
set:
installCRDs: true
- name: pangolin
namespace: pangolin
createNamespace: true
chart: fossorial/pangolin
version: 0.1.0-alpha.0
values:
- ./values/pangolin.yaml
dependsOn:
- cert-manager
- name: newt
namespace: pangolin
chart: fossorial/newt
version: 1.4.0
values:
- ./values/newt.yaml
dependsOn:
- pangolin
```
### 3. Create values files
Create `values/pangolin.yaml`:
```yaml
deployment:
type: controller
mode: multi
database:
mode: cloudnativepg
pangolin:
config:
app:
dashboard_url: https://pangolin.example.com
domains:
domain1:
base_domain: example.com
gerbil:
base_endpoint: vpn.example.com
ingress:
enabled: true
className: traefik
hosts:
- host: pangolin.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: pangolin-tls
hosts:
- pangolin.example.com
```
Create `values/newt.yaml`:
```yaml
newtInstances:
- name: main-tunnel
enabled: true
auth:
existingSecretName: newt-auth
```
### 4. Create Newt auth secret
Before applying Helmfile:
```bash
kubectl create namespace pangolin
kubectl create secret generic newt-auth \
-n pangolin \
--from-literal=PANGOLIN_ENDPOINT=https://pangolin.example.com \
--from-literal=NEWT_ID=<your-newt-id> \
--from-literal=NEWT_SECRET=<your-newt-secret>
```
### 5. Deploy with Helmfile
```bash
# Preview changes
helmfile diff
# Apply releases
helmfile sync
# or
helmfile apply
```
### 6. Verify deployment
```bash
helmfile status
# Check individual releases
helm status cert-manager -n cert-manager
helm status pangolin -n pangolin
helm status newt -n pangolin
# Check pods
kubectl get pods -n pangolin
kubectl get pods -n cert-manager
```
## Advanced: Helmfile with environments
For multi-environment setups (dev, staging, prod), use Helmfile environments:
```yaml
environments:
dev:
values:
environment: dev
domain: dev.example.com
replicaCount: 1
prod:
values:
environment: prod
domain: pangolin.example.com
replicaCount: 3
helmDefaults:
atomic: true
wait: true
repositories:
- name: fossorial
url: https://charts.fossorial.io
releases:
- name: pangolin
namespace: pangolin
createNamespace: true
chart: fossorial/pangolin
version: 0.1.0-alpha.0
values:
- ./values/pangolin-{{ .Environment.Values.environment }}.yaml
```
Deploy to specific environment:
```bash
helmfile -e dev sync
helmfile -e prod sync
```
## Helmfile with GitOps
### Using Helmfile with FluxCD
FluxCD can reconcile Helmfile declarations using the `helmfile-controller`. This allows Git-driven Helmfile updates:
1. Commit Helmfile and values to Git
2. Create HelmRelease for each release in your Helmfile
3. Flux reconciles and applies changes
See [Flux Guide](/self-host/manual/kubernetes/gitops/flux) for details.
### Using Helmfile with Argo CD
While Argo CD has native Helm and Kustomize support, you can:
1. Use Helmfile to render manifests: `helmfile template > manifests.yaml`
2. Commit manifests to Git
3. Have Argo CD manage the raw YAML
Alternatively, use Helm source in Argo CD (simpler than Helmfile for single releases).
## Troubleshooting Helmfile
### Check syntax
```bash
helmfile lint
```
### Debug release dependencies
```bash
helmfile template
```
### See what will be deployed
```bash
helmfile diff
```
### Remove releases
```bash
helmfile destroy
```
<Warning>
`helmfile destroy` uninstalls all releases and may delete data (e.g., databases). Use with caution in production.
</Warning>
## Common patterns
### Helmfile with local chart overrides
```yaml
releases:
- name: pangolin
namespace: pangolin
chart: ./charts/pangolin # local path
values:
- values.yaml
```
### Helmfile with inline values
```yaml
releases:
- name: pangolin
namespace: pangolin
chart: fossorial/pangolin
set:
deployment.type: controller
deployment.mode: multi
```
### Helmfile with conditional releases
```yaml
releases:
- name: cert-manager
namespace: cert-manager
createNamespace: true
chart: jetstack/cert-manager
installed: {{ .Environment.Values.installCertManager | default true }}
```
## Important notes
### Official support
Helmfile for Pangolin/Newt Kubernetes deployments is **advanced/community-supported**. The primary supported methods are:
- Helm directly
- Kustomize overlays
- GitOps tools (Argo CD, Flux)
If you encounter Helmfile-specific issues, refer to the [Helmfile documentation](https://github.com/roboll/helmfile) and community.
### Helm chart dependencies
The Pangolin Helm chart includes optional sub-chart dependencies (e.g., CloudNativePG operator). Helmfile does not manage these—they're handled by Helm. Ensure chart dependencies are available when installing.
## Next steps
<CardGroup cols={2}>
<Card title="Helm Quick-Start" href="/self-host/manual/kubernetes/helm" icon="box" />
<Card title="Pangolin Configuration" href="/self-host/manual/kubernetes/pangolin/configuration" icon="sliders" />
<Card title="GitOps with Flux" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch" />
<Card title="Troubleshooting" href="/self-host/manual/kubernetes/pangolin/troubleshooting" icon="circle-question" />
</CardGroup>
@@ -0,0 +1,372 @@
---
title: "Kustomize"
description: "Customize Helm-rendered Kubernetes manifests with Kustomize overlays."
---
Kustomize can be used to customize Kubernetes manifests with bases, overlays, and patches.
For Pangolin and Newt, the supported Kustomize workflow is to render the Helm charts into manifests and use those rendered manifests as the Kustomize base.
Use Kustomize when you need:
- environment-specific overlays for dev, staging, or production
- explicit manifest patches in Git
- a manifest-driven workflow for GitOps tools
- small changes on top of a shared base without maintaining separate full manifests
## Supported workflow
The chart repository does not provide native Kustomize bases. Use this workflow instead:
<Steps>
<Step title="Render chart manifests">
Render the Helm chart with your values file and save the output as base manifests.
</Step>
<Step title="Commit base manifests">
Commit rendered manifests as the Kustomize base in Git.
</Step>
<Step title="Create environment overlays">
Create overlays for each environment (for example dev, staging, production).
</Step>
<Step title="Apply or reconcile">
Apply overlays manually or reconcile them with Argo CD or Flux.
</Step>
</Steps>
<Warning>
Do not manage the same resources with both a live Helm release and Kustomize. Pick one ownership model per environment.
</Warning>
Recommended ownership model:
- Use Helm only to render manifests.
- Use Kustomize, Argo CD, or Flux to apply and reconcile the rendered manifests.
- Re-render the base when upgrading the chart version.
## Example repository layout
```text
my-pangolin-k8s/
├── base/
│ ├── kustomization.yaml
│ ├── pangolin.yaml
│ └── newt.yaml
├── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── pangolin-resources.patch.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── pangolin-resources.patch.yaml
│ └── prod/
│ ├── kustomization.yaml
│ └── pangolin-resources.patch.yaml
└── values/
├── values-pangolin.yaml
└── values-newt.yaml
```
## Step 1: Render manifests from Helm
Create a base directory:
```bash
mkdir -p base overlays/dev overlays/staging overlays/prod
```
Render Pangolin:
```bash tab="Classic Helm repository"
helm template pangolin fossorial/pangolin \
--namespace pangolin \
--values values/values-pangolin.yaml \
> base/pangolin.yaml
```
```bash tab="OCI (GHCR)"
helm template pangolin oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0 \
--namespace pangolin \
--values values/values-pangolin.yaml \
> base/pangolin.yaml
```
Render Newt:
```bash tab="Classic Helm repository"
helm template newt fossorial/newt \
--namespace pangolin \
--values values/values-newt.yaml \
> base/newt.yaml
```
```bash tab="OCI (GHCR)"
helm template newt oci://ghcr.io/fosrl/helm-charts/newt \
--version 1.4.0 \
--namespace pangolin \
--values values/values-newt.yaml \
> base/newt.yaml
```
## Step 2: Create the base kustomization
```yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pangolin.yaml
- newt.yaml
```
## Step 3: Create an overlay
Use `resources` to reference the base.
```yaml
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
labels:
- pairs:
app.kubernetes.io/environment: production
app.kubernetes.io/managed-by: kustomize
patches:
- path: pangolin-resources.patch.yaml
target:
group: apps
version: v1
kind: Deployment
name: pangolin
```
<Note>
Avoid `namePrefix` and `nameSuffix` for Helm-rendered bases unless you have verified every generated reference. Renaming chart-generated resources can break service names, selectors, secret references, and workload dependencies.
</Note>
## Step 4: Add patches
Example Strategic Merge patch for container resources:
```yaml
# overlays/prod/pangolin-resources.patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pangolin
spec:
template:
spec:
containers:
- name: pangolin
resources:
requests:
cpu: 1000m
memory: 1Gi
limits:
memory: 2Gi
```
Example JSON6902-style inline patch:
```yaml
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- target:
group: apps
version: v1
kind: Deployment
name: pangolin
patch: |-
- op: replace
path: /spec/template/spec/containers/0/resources/requests/cpu
value: "1000m"
```
<Note>
Modern Kustomize uses the `patches` field for both Strategic Merge and JSON6902-style patches. Avoid `patchesStrategicMerge`, `patchesJson6902`, and `bases` in new examples.
</Note>
## Apply an overlay
Preview the rendered output:
```bash
kustomize build overlays/prod
```
Compare with the live cluster:
```bash
kustomize build overlays/prod | kubectl diff -f -
```
Apply the overlay:
```bash
kubectl apply -k overlays/prod
```
Or apply the rendered output:
```bash
kustomize build overlays/prod | kubectl apply -f -
```
## Updating the base
When upgrading chart versions or changing Helm values, re-render the base and review the diff.
```bash
helm repo update fossorial
```
Render the updated chart output:
```bash
helm template pangolin fossorial/pangolin \
--namespace pangolin \
--values values/values-pangolin.yaml \
> base/pangolin.yaml
```
```bash
helm template newt fossorial/newt \
--namespace pangolin \
--values values/values-newt.yaml \
> base/newt.yaml
```
Then validate the overlay:
```bash
kustomize build overlays/prod
```
Review changes before applying:
```bash
git diff
kustomize build overlays/prod | kubectl diff -f -
```
Apply after review:
```bash
kubectl apply -k overlays/prod
```
## Important considerations
### Namespace handling
Render the charts with the namespace you intend to use:
```bash
helm template pangolin fossorial/pangolin \
--namespace pangolin \
--values values/values-pangolin.yaml \
> base/pangolin.yaml
```
Create the namespace before applying the overlay:
```bash
kubectl create namespace pangolin
```
Apply any required Pod Security Admission labels or cluster-policy labels before workloads are created.
### Secrets
Do not commit plaintext secrets into rendered manifests.
Use one of these approaches instead:
* reference existing Kubernetes Secrets in the values file before rendering
* create secrets separately with your secret-management workflow
* use Sealed Secrets, External Secrets Operator, SOPS, or another GitOps-safe secret solution
### Do not mix ownership models
Avoid this pattern:
```text
helm upgrade pangolin fossorial/pangolin
kubectl apply -k overlays/prod
```
This creates two tools managing the same objects.
Use one of these models instead:
| Model | Description |
| ----------------- | ---------------------------------------------------------------------------------------- |
| Helm-managed | Helm installs and upgrades the live release. Kustomize is not used for the same objects. |
| Kustomize-managed | Helm only renders the base. Kustomize applies and owns the live objects. |
| GitOps-managed | Argo CD or Flux applies the Kustomize overlay and owns reconciliation. |
## Troubleshooting
Validate the overlay:
```bash
kustomize build overlays/prod
```
Check the generated YAML:
```bash
kustomize build overlays/prod > manifests.yaml
```
Run a server-side dry run:
```bash
kubectl apply -f manifests.yaml --dry-run=server
```
Preview live changes:
```bash
kubectl diff -f manifests.yaml
```
Check live resources:
```bash
kubectl get all -n pangolin
kubectl get events -n pangolin --sort-by=.lastTimestamp
```
## Next steps
<CardGroup cols={2}>
<Card title="Pangolin Kustomize Install" href="/self-host/manual/kubernetes/pangolin/kustomize" icon="server">
Install Pangolin with rendered manifests and Kustomize overlays.
</Card>
<Card title="Newt Kustomize Install" href="/manage/sites/kubernetes/kustomize" icon="globe">
Install Newt with rendered manifests and Kustomize overlays.
</Card>
<Card title="Argo CD" href="/self-host/manual/kubernetes/gitops/argocd" icon="code-branch">
Reconcile Kustomize overlays with Argo CD.
</Card>
<Card title="Flux" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch">
Reconcile Kustomize overlays with Flux.
</Card>
<Card title="Troubleshooting" href="/self-host/manual/kubernetes/pangolin/troubleshooting" icon="circle-question">
Troubleshoot Pangolin deployments on Kubernetes.
</Card>
</CardGroup>
@@ -0,0 +1,77 @@
---
title: "Overview"
description: "Deploy Pangolin, Sites (Newt), and related components on Kubernetes."
---
## Components
| Component | Role |
| --- | --- |
| Pangolin | Main application for the dashboard, API, authentication, configuration, and database-backed state. |
| Gerbil | Tunnel stack component used by Pangolin for site connectivity. |
| Site (Newt) | Site connector used to connect private resources to Pangolin. |
| Traefik | Reverse proxy and router for ingress traffic. |
| PostgreSQL / SQLite | Database options for Pangolin deployments, depending on the selected chart configuration. |
| Pangolin Kube Controller | Kubernetes controller for integrating Pangolin with Kubernetes and Traefik resources. |
<Info>
Depending on your deployment mode, not every component is required. Local reverse proxy deployments and tunneled site deployments can have different component requirements.
</Info>
```mermaid
flowchart LR
U[Users] --> T[Traefik]
T --> P[Pangolin]
P --> G[Gerbil]
S[Site connector<br/>Newt] --> G
P --> D[(Database)]
```
## Installation paths
<CardGroup cols={2}>
<Card title="Choose an Installation Path" href="/self-host/manual/kubernetes/choose-method" icon="route">
Pick the Kubernetes workflow that matches how you deploy applications.
</Card>
<Card title="Prerequisites" href="/self-host/manual/kubernetes/prerequisites" icon="list-check">
Review the required cluster, ingress, DNS, storage, and secret setup.
</Card>
<Card title="Helm Quick-Start" href="/self-host/manual/kubernetes/helm" icon="box">
Install Pangolin or Sites (Newt) with the standard chart-based workflow.
</Card>
<Card title="Kustomize Quick-Start" href="/self-host/manual/kubernetes/kustomize" icon="layer-group">
Use overlays and patches for manifest-based deployments.
</Card>
<Card title="Argo CD Guide" href="/self-host/manual/kubernetes/gitops/argocd" icon="code-branch">
Deploy Pangolin or Sites (Newt) with Argo CD.
</Card>
<Card title="Flux Guide" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch">
Deploy Pangolin or Sites (Newt) with Flux.
</Card>
<Card title="Helmfile Guide" href="/self-host/manual/kubernetes/helmfile" icon="boxes-stacked">
Manage multiple Helm releases together.
</Card>
</CardGroup>
## Component guides
<CardGroup cols={2}>
<Card title="Pangolin with Helm" href="/self-host/manual/kubernetes/pangolin/helm" icon="server">
Install Pangolin with the Helm chart.
</Card>
<Card title="Pangolin Configuration" href="/self-host/manual/kubernetes/pangolin/configuration" icon="sliders">
Configure Pangolin for your Kubernetes environment.
</Card>
<Card title="Pangolin Troubleshooting" href="/self-host/manual/kubernetes/pangolin/troubleshooting" icon="circle-question">
Diagnose and resolve Pangolin deployment issues.
</Card>
<Card title="Site (Newt) Helm" href="/manage/sites/kubernetes/helm" icon="server">
Install a Site connector with the Newt Helm chart.
</Card>
<Card title="Site (Newt) Configuration" href="/manage/sites/kubernetes/configuration" icon="sliders">
Configure Site connector credentials and runtime settings.
</Card>
<Card title="Site (Newt) Troubleshooting" href="/manage/sites/kubernetes/troubleshooting" icon="circle-question">
Diagnose and resolve Site connector deployment issues.
</Card>
</CardGroup>
@@ -0,0 +1,994 @@
---
title: "Configuration"
description: "Configuration reference for Pangolin Kubernetes deployments."
---
This page covers the main Pangolin Kubernetes configuration options for Helm and Kustomize workflows.
For exhaustive option coverage, refer to the chart resources:
<CardGroup cols={3}>
<Card title="README" href="https://github.com/fosrl/helm-charts/blob/main/charts/pangolin/README.md" />
<Card title="values.yaml" href="https://github.com/fosrl/helm-charts/blob/main/charts/pangolin/values.yaml" />
<Card title="values.schema.json" href="https://github.com/fosrl/helm-charts/blob/main/charts/pangolin/values.schema.json" />
</CardGroup>
## Version context
This page is aligned with the Pangolin Helm chart `0.1.0-alpha.0`.
| Item | Value |
| --- | --- |
| Chart version | `0.1.0-alpha.0` |
| Pangolin app version | `1.18.2` |
| Kubernetes version | `>=1.30.14-0` |
| Gerbil image tag | `1.3.1` |
| pangolin-kube-controller image tag | `0.1.0-alpha.1` |
| Traefik image tag | `v3.6.15` |
## Configuration sections
<AccordionGroup>
<Accordion title="Deployment topology" defaultOpen>
Control how Pangolin components are deployed and integrated with Kubernetes.
```yaml
deployment:
type: controller
mode: multi
installTraefikController: false
traefikNamespace: ""
```
Recommended production topology:
```yaml
deployment:
type: controller
mode: multi
```
| Setting | Description |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `deployment.type=controller` | Uses `pangolin-kube-controller` and Traefik CRDs. Recommended for Kubernetes deployments. |
| `deployment.type=standalone` | Runs an internal Traefik workload managed by this chart. Mainly useful for labs and self-contained deployments. |
| `deployment.mode=multi` | Runs Pangolin, Gerbil, and controller/Traefik components as separate workloads. Recommended for production. |
| `deployment.mode=single` | Runs multiple components in one shared Pod. Useful only when you explicitly need a compact topology. |
| `deployment.installTraefikController=true` | Installs the bundled Traefik dependency in controller mode. |
| `deployment.traefikNamespace` | Namespace where Traefik controller resources live. Defaults to the release namespace when empty. |
<Note>
In controller mode, Traefik CRDs and a Traefik controller must be available. You can install Traefik separately or enable the bundled Traefik dependency with `deployment.installTraefikController=true`.
</Note>
If you enable the bundled Traefik dependency, put Traefik chart overrides under the `traefikController` key.
</Accordion>
<Accordion title="Namespace and Pod Security Admission">
Namespace creation is controlled by the `namespace` block.
```yaml
namespace:
create: false
name: ""
labels: {}
podSecurity:
enforce: ""
warn: ""
audit: ""
```
Recommended pattern:
1. Create the namespace manually.
2. Apply the required labels and annotations.
3. Install the chart into that namespace.
```bash
kubectl create namespace pangolin
```
Gerbil requires `NET_ADMIN` for WireGuard interface management. If your cluster enforces Pod Security Admission, the namespace must allow that capability.
Example:
```bash
kubectl label namespace pangolin \
pod-security.kubernetes.io/enforce=privileged \
--overwrite
```
If you let the chart create the namespace, configure the Pod Security labels through values:
```yaml
namespace:
create: true
name: pangolin
podSecurity:
enforce: privileged
warn: baseline
audit: restricted
```
<Warning>
Do not apply a restricted Pod Security profile to a namespace running Gerbil unless you have validated WireGuard functionality. Gerbil requires `NET_ADMIN`; removing it breaks tunnel management.
</Warning>
</Accordion>
<Accordion title="Database modes">
Choose the database backend for Pangolin.
```yaml
database:
mode: cloudnativepg
name: pangolin
username: pangolin
```
Supported modes:
| Mode | Use case |
| --------------- | --------------------------------------------------------------------- |
| `cloudnativepg` | Recommended production mode using CloudNativePG. This is the default. |
| `external` | Production mode with an externally managed PostgreSQL database. |
| `embedded` | Chart-managed PostgreSQL for labs and test environments. |
| `sqlite` | Development or CI only. Not recommended for production. |
### CloudNativePG
The default database mode is `cloudnativepg`.
```yaml
database:
mode: cloudnativepg
cloudnativepg:
cluster:
name: pangolin-db
connection:
database: pangolin
username: pangolin
sslMode: disable
cnpg-operator:
enabled: false
cnpg-cluster:
enabled: false
fullnameOverride: pangolin-db
```
CloudNativePG can be used in four common ways:
| Mode | Values |
| -------------------------------------- | ----------------------------------------------------------- |
| Existing operator and existing cluster | `cnpg-operator.enabled=false`, `cnpg-cluster.enabled=false` |
| Chart installs operator only | `cnpg-operator.enabled=true`, `cnpg-cluster.enabled=false` |
| Chart installs cluster only | `cnpg-operator.enabled=false`, `cnpg-cluster.enabled=true` |
| Chart installs operator and cluster | `cnpg-operator.enabled=true`, `cnpg-cluster.enabled=true` |
When `cnpg-cluster.enabled=true`, keep the CNPG cluster name consistent:
```yaml
database:
cloudnativepg:
cluster:
name: pangolin-db
cnpg-cluster:
enabled: true
fullnameOverride: pangolin-db
```
For the default CNPG cluster name `pangolin-db`, CloudNativePG creates an application Secret named `pangolin-db-app` with the key `uri`. The chart can automatically use this default Secret when no explicit `database.connection.existingSecretName` is set.
Explicit Secret reference:
```yaml
database:
connection:
existingSecretName: pangolin-db-app
existingSecretKey: uri
```
### External PostgreSQL
For an external PostgreSQL database, prefer a Kubernetes Secret containing the final connection string.
```yaml
database:
mode: external
connection:
existingSecretName: pangolin-db-connection
existingSecretKey: connectionString
```
The Secret should contain a PostgreSQL connection string:
```bash
kubectl create secret generic pangolin-db-connection \
--namespace pangolin \
--from-literal=connectionString='postgresql://pangolin:password@postgres.example.com:5432/pangolin?sslmode=require'
```
You can also let the chart create a connection Secret from values:
```yaml
database:
mode: external
external:
generatedSecret:
create: true
host: postgres.example.com
port: 5432
database: pangolin
username: pangolin
password: "<password>"
sslMode: require
```
<Warning>
Avoid storing database passwords directly in values files for production. Use an existing Secret or your normal secret-management workflow.
</Warning>
### Embedded PostgreSQL
Embedded PostgreSQL is intended for labs and tests.
```yaml
database:
mode: embedded
embedded:
persistence:
enabled: true
size: 8Gi
```
### SQLite
SQLite is only suitable for development, CI, or very small test deployments.
```yaml
database:
mode: sqlite
sqlite:
persistence:
enabled: true
size: 1Gi
```
</Accordion>
<Accordion title="Pangolin application config">
The `pangolin.config` block renders `/app/config/config.yml`.
```yaml
pangolin:
config:
app:
dashboard_url: "https://pangolin.example.com"
log_level: info
domains:
domain1:
base_domain: "example.com"
cert_resolver: "letsencrypt"
gerbil:
start_port: 51820
clients_start_port: 21820
base_endpoint: "pangolin.example.com"
traefik:
enabled: true
http_entrypoint: web
https_entrypoint: websecure
cert_resolver: letsencrypt
```
Important settings:
| Setting | Description |
| ------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `pangolin.config.app.dashboard_url` | Public dashboard URL. Set this to the real user-facing URL. |
| `pangolin.config.domains` | Domain map used by Pangolin. Replace the default `example.com` entry before production use. |
| `pangolin.config.gerbil.base_endpoint` | Public hostname or IP where Gerbil is reachable. |
| `pangolin.config.gerbil.start_port` | First WireGuard site port. Keep this aligned with `gerbil.ports.wg1`. |
| `pangolin.config.gerbil.clients_start_port` | Client WireGuard port. Keep this aligned with `gerbil.ports.wg2`. |
| `pangolin.config.traefik.enabled` | Includes Pangolin's Traefik config section. This does not install Traefik. |
| `pangolin.config.traefik.cert_resolver` | ACME resolver name used in Pangolin-generated Traefik configuration. |
<Note>
`pangolin.config.traefik` controls the Traefik configuration generated by Pangolin. Traefik installation is controlled separately through controller mode, the bundled Traefik dependency, or standalone Traefik mode.
</Note>
### Pangolin app secret
Pangolin requires `SERVER_SECRET`.
Use an existing Secret for production:
```yaml
pangolin:
secret:
existingSecretName: pangolin-app-secret
existingSecretKey: SERVER_SECRET
```
Create the Secret:
```bash
kubectl create secret generic pangolin-app-secret \
--namespace pangolin \
--from-literal=SERVER_SECRET='<strong-random-secret>'
```
If no existing Secret is provided, the chart can generate one:
```yaml
pangolin:
secret:
generated:
create: true
key: SERVER_SECRET
length: 64
```
<Warning>
Do not commit plaintext secrets to Git. For GitOps workflows, use SOPS, Sealed Secrets, External Secrets Operator, Vault, Infisical, or a cloud secret manager.
</Warning>
</Accordion>
<Accordion title="Dashboard IngressRoute">
In controller mode, the chart can render a Traefik `IngressRoute` for the Pangolin dashboard and API.
```yaml
pangolin:
ingressRoute:
dashboard:
enabled: true
host: ""
ingressClassName: ""
traefikSelectorLabels: {}
entryPoints:
- websecure
routes:
api:
enabled: true
pathPrefix: /api/v1
priority: 100
dashboard:
enabled: true
priority: 10
tls:
enabled: true
certResolver: ""
secretName: ""
```
Default routing behavior:
| Route | Match | Backend port |
| --------- | ---------------------------------- | ------------------------------------------------- |
| API | `Host(...) && PathPrefix(/api/v1)` | `pangolin.service.ports.external`, default `3000` |
| Dashboard | `Host(...)` | `pangolin.service.ports.next`, default `3002` |
The host defaults to the hostname from `pangolin.config.app.dashboard_url`. You can override it with:
```yaml
pangolin:
ingressRoute:
dashboard:
host: pangolin.example.com
```
### TLS with certResolver
```yaml
pangolin:
config:
traefik:
cert_resolver: letsencrypt
ingressRoute:
dashboard:
tls:
enabled: true
certResolver: letsencrypt
secretName: ""
```
### TLS with existing Secret
```yaml
pangolin:
ingressRoute:
dashboard:
tls:
enabled: true
certResolver: ""
secretName: pangolin-dashboard-tls
```
<Warning>
`tls.certResolver` and `tls.secretName` are mutually exclusive. Use one or the other.
</Warning>
### Multi-Traefik setups
Use labels to target a specific Traefik CRD provider when multiple Traefik instances watch different label selectors:
```yaml
pangolin:
ingressRoute:
dashboard:
traefikSelectorLabels:
traefik-instance: public
```
You can also set an ingress class annotation:
```yaml
pangolin:
ingressRoute:
dashboard:
ingressClassName: traefik-public
```
</Accordion>
<Accordion title="Gerbil">
Gerbil manages WireGuard tunnel connectivity for Pangolin.
```yaml
gerbil:
enabled: true
startupMode: normal
ports:
wg1: 51820
wg2: 21820
internalApi: 3004
service:
enabled: true
type: ClusterIP
persistence:
enabled: true
size: 1Gi
```
Important settings:
| Setting | Description |
| ---------------------------- | ----------------------------------------------------------------------------------------- |
| `gerbil.enabled` | Enables the Gerbil component. |
| `gerbil.startupMode` | Controls first-run and normal startup behavior. |
| `gerbil.ports.wg1` | First WireGuard UDP port. Keep aligned with `pangolin.config.gerbil.start_port`. |
| `gerbil.ports.wg2` | Second WireGuard UDP port. Keep aligned with `pangolin.config.gerbil.clients_start_port`. |
| `gerbil.ports.internalApi` | Internal Gerbil API/listener port. |
| `gerbil.service.enabled` | Creates a Service for Gerbil UDP traffic. |
| `gerbil.persistence.enabled` | Persists Gerbil key/config data. Recommended for production. |
<Info>
If Gerbil is exposed through a reverse proxy or UDP gateway, keep proxy protocol settings aligned end-to-end. Do not enable proxy protocol on the upstream hop unless Gerbil is configured to accept it.
</Info>
### Startup mode
```yaml
gerbil:
startupMode: delayed
```
| Mode | Behavior |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `normal` | Starts Gerbil immediately. Use after Pangolin setup is complete. |
| `delayed` | Renders Gerbil resources but keeps the Deployment at `replicas: 0` in multi mode. Useful for first installs and smoke tests. |
| `disabledUntilSetup` | Does not render Gerbil resources until switched back to `normal` or `delayed`. |
For first installs, `delayed` can help when Gerbil would otherwise fail before the initial Pangolin setup is complete.
Switch back after setup:
```bash
helm upgrade pangolin fossorial/pangolin \
--namespace pangolin \
--reuse-values \
--set gerbil.startupMode=normal
```
### Security
Gerbil requires `NET_ADMIN`.
```yaml
gerbil:
securityContext:
runAsNonRoot: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
capabilities:
add:
- NET_ADMIN
drop:
- ALL
```
<Warning>
Do not remove `NET_ADMIN` from Gerbil. Without it, Gerbil cannot create or manage WireGuard interfaces. `SYS_MODULE` is not added by default and should only be added when your node kernel requires module loading from inside the container.
</Warning>
</Accordion>
<Accordion title="NetworkPolicy">
NetworkPolicy rendering is enabled by default.
<Note>
The chart-managed NetworkPolicies are intended to allow required Pangolin, Gerbil, database, DNS, and controller traffic for standard deployments.
</Note>
```yaml
networkPolicy:
enabled: true
allowExternalIngress: true
allowExternalEgressHttps: false
dns:
enabled: true
database:
enabled: true
port: 5432
controller:
egress:
enabled: true
kubernetesApi:
enabled: true
cidr: ""
port: 443
metrics:
enabled: false
gerbil:
allowWireguardUdpEgress: true
wireguardUdpCIDRs:
- 0.0.0.0/0
```
Important defaults:
| Setting | Default | Notes |
| ------------------------------------------------------- | ------- | -------------------------------------------------------------------------------- |
| `networkPolicy.enabled` | `true` | Renders NetworkPolicy resources. |
| `networkPolicy.allowExternalIngress` | `true` | Allows public ingress to exposed services controlled by the chart. |
| `networkPolicy.allowExternalEgressHttps` | `false` | Broad HTTPS egress is not allowed by default. Prefer scoped `extraEgress` rules. |
| `networkPolicy.dns.enabled` | `true` | Allows DNS egress. |
| `networkPolicy.database.enabled` | `true` | Adds database egress rules for Pangolin. |
| `networkPolicy.controller.egress.kubernetesApi.enabled` | `true` | Allows controller API-server access when configured. |
| `networkPolicy.gerbil.allowWireguardUdpEgress` | `true` | Allows Gerbil UDP egress for WireGuard peer traffic. |
When tightening policies, verify these paths:
* DNS egress
* Pangolin to database
* controller to Kubernetes API
* ingress controller to Pangolin service
* Gerbil UDP traffic
* outbound access for SMTP, OIDC, webhooks, or other external integrations
Use component-scoped rules where possible:
```yaml
networkPolicy:
pangolin:
extraEgress: []
controller:
extraEgress: []
gerbil:
extraEgress: []
```
<Warning>
If you disable or replace chart-managed NetworkPolicies, ensure your custom policies still allow all required traffic paths.
</Warning>
</Accordion>
<Accordion title="Monitoring">
The chart has chart-level monitoring settings for Pangolin and controller-specific monitoring settings for `pangolin-kube-controller`.
### Pangolin monitoring
```yaml
monitoring:
enabled: false
service:
enabled: false
type: ClusterIP
port: 9090
portName: metrics
metrics:
targetPortName: metrics
targetPort: 9090
path: /metrics
```
### Controller monitoring
```yaml
controller:
service:
enabled: true
port: 9090
portName: metrics
monitoring:
serviceMonitor:
enabled: false
podMonitor:
enabled: false
prometheusRule:
enabled: false
```
Enable controller ServiceMonitor when Prometheus Operator is available:
```yaml
controller:
monitoring:
serviceMonitor:
enabled: true
```
Enable chart-level metrics Service when the Pangolin app exposes metrics in your selected configuration:
```yaml
monitoring:
enabled: true
service:
enabled: true
```
<Note>
Only enable ServiceMonitor, PodMonitor, or PrometheusRule resources when the matching CRDs are installed in the cluster.
</Note>
</Accordion>
<Accordion title="ServiceAccount and RBAC">
The chart uses separate ServiceAccounts for Pangolin, Gerbil, and the controller in multi mode.
```yaml
serviceAccount:
pangolin:
create: true
automountServiceAccountToken: false
gerbil:
create: true
automountServiceAccountToken: false
controller:
create: true
automountServiceAccountToken: true
rbac:
create: true
```
Default behavior:
| Component | API token mounted by default | Reason |
| ---------- | ---------------------------- | ----------------------------------------------------------------------- |
| Pangolin | No | The app does not need Kubernetes API access. |
| Gerbil | No | Gerbil manages WireGuard and does not need Kubernetes API access. |
| Controller | Yes | The controller reconciles Traefik CRDs and needs Kubernetes API access. |
<Note>
In `deployment.mode=single` with `deployment.type=controller`, Kubernetes ServiceAccount selection is Pod-level. The shared Pod uses the controller ServiceAccount and token.
</Note>
</Accordion>
<Accordion title="Resources, scheduling, and images">
Global scheduling defaults:
```yaml
global:
storageClass: ""
image:
registry: docker.io
imagePullPolicy: IfNotPresent
imagePullSecrets: []
nodeSelector: {}
tolerations: []
affinity: {}
topologySpreadConstraints: []
priorityClassName: ""
```
Resource rendering policy:
```yaml
resourcesPolicy:
cpuLimits:
enabled: true
ephemeralStorage:
enabled: false
```
<Warning>
CPU limits can cause throttling even when spare CPU exists on the node. For most deployments, start with CPU requests and memory limits, then add CPU limits only when explicitly required.
</Warning>
Pangolin resources:
```yaml
pangolin:
resources:
requests:
cpu: 200m
memory: 256Mi
ephemeral-storage: 32Mi
limits:
cpu: 1000m
memory: 1Gi
ephemeral-storage: 256Mi
```
Gerbil resources:
```yaml
gerbil:
resources:
requests:
cpu: 100m
memory: 128Mi
ephemeral-storage: 16Mi
limits:
cpu: 500m
memory: 512Mi
ephemeral-storage: 128Mi
```
Controller resources:
```yaml
controller:
resources:
requests:
cpu: 100m
memory: 128Mi
ephemeral-storage: 16Mi
limits:
cpu: 500m
memory: 512Mi
ephemeral-storage: 128Mi
```
Image configuration:
```yaml
images:
pangolin:
registry: docker.io
repository: fosrl/pangolin
tag: ""
digest: ""
pangolinPostgresql:
registry: docker.io
repository: fosrl/pangolin
tag: ""
digest: ""
gerbil:
registry: docker.io
repository: fosrl/gerbil
tag: "1.3.1"
digest: ""
controller:
registry: ghcr.io
repository: fosrl/pangolin-kube-controller
tag: "0.1.0-alpha.1"
digest: ""
traefik:
registry: docker.io
repository: traefik
tag: v3.6.15
digest: ""
```
The chart automatically selects the PostgreSQL-capable Pangolin image variant for non-SQLite database modes unless you override the Pangolin tag or digest.
<Note>
Ephemeral-storage requests and limits are only rendered when `resourcesPolicy.ephemeralStorage.enabled=true`.
</Note>
</Accordion>
<Accordion title="Standalone Traefik">
Standalone Traefik is used mainly when `deployment.type=standalone`.
```yaml
traefik:
enabled: false
service:
enabled: true
type: LoadBalancer
config:
dashboard: false
httpEntrypoint: web
httpsEntrypoint: websecure
certResolver: letsencrypt
letsencryptEmail: ""
persistence:
enabled: false
```
Important notes:
- `traefik.enabled=true` runs an internal Traefik workload managed by this chart.
- `traefik.config.letsencryptEmail` is required when standalone Traefik is enabled.
- If you enable the Traefik dashboard, enable `traefik.persistence.enabled` so ACME state survives restarts.
- In controller mode, prefer using an existing or bundled Traefik controller instead of standalone Traefik.
</Accordion>
<Accordion title="Blueprint storage">
The chart can store Pangolin Blueprint YAML files as Kubernetes ConfigMaps and Secrets.
```yaml
pangolin:
blueprints:
enabled: false
configMap:
create: true
files: {}
environmentSecret:
create: true
existingConfigMap: ""
existingEnvironmentSecret: ""
```
Example:
```yaml
pangolin:
blueprints:
enabled: true
configMap:
create: true
files:
site-blueprint.yaml: |
sites:
my-site:
name: My Site
public-resources:
web-app:
name: Web Application
protocol: http
full-domain: "app.example.com"
targets:
- site: my-site
hostname: app
port: 8080
method: http
```
Sensitive blueprint environment values should come from a Secret:
```yaml
pangolin:
blueprints:
enabled: true
existingConfigMap: my-blueprint-configmap
existingEnvironmentSecret: my-blueprint-env
```
<Warning>
The Pangolin server does not apply Blueprint files directly. Blueprints are applied by Pangolin Sites through the Pangolin API using `--blueprint-file` or `--provisioning-blueprint-file`.
</Warning>
</Accordion>
</AccordionGroup>
## Configuration by install method
### Helm
Use a values file:
```bash
helm upgrade --install pangolin fossorial/pangolin \
--namespace pangolin \
--values values-pangolin.yaml
```
Use inline values only for small tests:
```bash
helm upgrade --install pangolin fossorial/pangolin \
--namespace pangolin \
--set deployment.type=controller \
--set deployment.mode=multi \
--set database.mode=cloudnativepg \
--set pangolin.config.app.dashboard_url=https://pangolin.example.com
```
See [Pangolin Helm](/self-host/manual/kubernetes/pangolin/helm) for the installation flow.
For complete application configuration keys and examples, see:
- [Public config file reference](/self-host/advanced/config-file)
- [Private config file reference](/self-host/advanced/private-config-file)
### Kustomize
Render the chart with Helm, then apply Kustomize overlays:
```bash
helm template pangolin fossorial/pangolin \
--namespace pangolin \
--values values-pangolin.yaml \
> base/pangolin.yaml
```
Apply the overlay:
```bash
kubectl apply -k overlays/prod
```
See [Pangolin Kustomize](/self-host/manual/kubernetes/pangolin/kustomize) for the Kustomize workflow.
### GitOps
Store Helm values or Kustomize overlays in Git. Argo CD or Flux reconciles the desired state.
Argo CD Helm example:
```yaml
spec:
source:
helm:
values: |
deployment:
type: controller
mode: multi
database:
mode: cloudnativepg
```
Flux HelmRelease example:
```yaml
spec:
values:
deployment:
type: controller
mode: multi
database:
mode: cloudnativepg
```
See [GitOps](/self-host/manual/kubernetes/gitops/overview) for GitOps guidance.
## Next steps
<CardGroup cols={2}>
<Card title="Helm Install" href="/self-host/manual/kubernetes/pangolin/helm" icon="box">
Install Pangolin with Helm.
</Card>
<Card title="Kustomize Install" href="/self-host/manual/kubernetes/pangolin/kustomize" icon="layer-group">
Install Pangolin with rendered manifests and Kustomize overlays.
</Card>
<Card title="Troubleshooting" href="/self-host/manual/kubernetes/pangolin/troubleshooting" icon="circle-question">
Debug Pangolin deployments on Kubernetes.
</Card>
<Card title="GitOps" href="/self-host/manual/kubernetes/gitops/overview" icon="code-branch">
Deploy Pangolin with Argo CD or Flux.
</Card>
</CardGroup>
@@ -0,0 +1,398 @@
---
title: "Helm"
description: "Quick-start guide for installing Pangolin on Kubernetes using Helm."
---
<Warning>
The Pangolin Helm chart is currently alpha (`0.1.0-alpha.0`). Test installs and upgrades in a non-production environment before using the chart for production traffic.
</Warning>
## What Pangolin deploys
The Pangolin Helm chart deploys the Pangolin control plane and related Kubernetes components.
Depending on the selected values, the chart can deploy:
- **Pangolin application**: dashboard, API, authentication, configuration, and application state.
- **pangolin-kube-controller**: Kubernetes controller used in controller mode.
- **Gerbil**: WireGuard tunnel manager used by the Pangolin tunnel stack.
- **Traefik integration**: Traefik CRD-based routing in controller mode, bundled Traefik controller when enabled, or standalone Traefik mode.
- **Database backend**: CloudNativePG, external PostgreSQL, embedded PostgreSQL, or SQLite.
See [Version Matrix](https://github.com/fosrl/helm-charts/VERSION_MATRIX.md) for chart and default app version references.
## Gerbil setup in the Pangolin chart
This chart deploys Gerbil when `gerbil.enabled=true`. This is the default when using `deployment.type=controller` and recommended.
<Info>
If Gerbil is exposed through a reverse proxy or UDP gateway, keep proxy protocol settings aligned end-to-end. Do not enable proxy protocol on the upstream hop unless Gerbil is configured to accept it.
</Info>
## Prerequisites
Before installing Pangolin, you need:
- Kubernetes `1.30.14` or newer.
- Helm 3.x.
- `kubectl` access to the target cluster.
- A namespace prepared for the install.
- A StorageClass if you use chart-managed persistent storage.
- DNS records for the Pangolin dashboard and tunnel endpoint.
- Traefik CRDs and a Traefik controller when using `deployment.type=controller`.
- A database plan: CloudNativePG, external PostgreSQL, embedded PostgreSQL, or SQLite.
See [Prerequisites](/self-host/manual/kubernetes/prerequisites) for detailed cluster, namespace, storage, networking, and security requirements.
## Recommended quick install
This quick install uses:
- `deployment.type=controller`
- `deployment.mode=multi`
- `database.mode=cloudnativepg`
- chart-managed CloudNativePG operator and cluster
- chart-managed dashboard `IngressRoute`
- Traefik cert resolver for TLS
<Note>
This example assumes a Traefik controller is available and can process the chart-managed `IngressRoute`. If you want the chart to install the bundled Traefik controller, set `deployment.installTraefikController=true`.
</Note>
### Step 1: Create the namespace
Create the namespace before installing the chart:
```bash
kubectl create namespace pangolin
```
Gerbil requires `NET_ADMIN` for WireGuard interface management. If your cluster enforces Pod Security Admission, label the namespace accordingly:
```bash
kubectl label namespace pangolin \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/audit=restricted \
--overwrite
```
<Warning>
Do not use a restricted Pod Security profile for a namespace running Gerbil unless you have validated the selected chart mode. Gerbil requires `NET_ADMIN` for WireGuard.
</Warning>
### Step 2: Create a Pangolin app secret
Create a Secret for `SERVER_SECRET`:
```bash
kubectl create secret generic pangolin-app-secret \
--namespace pangolin \
--from-literal=SERVER_SECRET='<strong-random-secret>'
```
Use a long random value. Do not commit this secret to Git.
### Step 3: Create a values file
Create `values-pangolin.yaml`:
```yaml
deployment:
type: controller
mode: multi
installTraefikController: false
database:
mode: cloudnativepg
cloudnativepg:
cluster:
name: pangolin-db
cnpg-operator:
enabled: true
cnpg-cluster:
enabled: true
fullnameOverride: pangolin-db
cluster:
instances: 1
storage:
size: 8Gi
pangolin:
secret:
existingSecretName: pangolin-app-secret
existingSecretKey: SERVER_SECRET
config:
app:
dashboard_url: https://pangolin.example.com
domains:
domain1:
base_domain: example.com
cert_resolver: letsencrypt
gerbil:
base_endpoint: vpn.example.com
start_port: 51820
clients_start_port: 21820
traefik:
enabled: true
http_entrypoint: web
https_entrypoint: websecure
cert_resolver: letsencrypt
ingressRoute:
dashboard:
enabled: true
host: pangolin.example.com
entryPoints:
- websecure
tls:
enabled: true
certResolver: letsencrypt
secretName: ""
gerbil:
enabled: true
startupMode: delayed
persistence:
enabled: true
size: 1Gi
```
Important points:
* Replace `pangolin.example.com`, `example.com`, and `vpn.example.com`.
* Keep `pangolin.config.gerbil.start_port` aligned with `gerbil.ports.wg1`.
* Keep `pangolin.config.gerbil.clients_start_port` aligned with `gerbil.ports.wg2`.
* Use `gerbil.startupMode=delayed` for the first install if Gerbil should not start before the initial Pangolin setup is complete.
The chart defaults to `deployment.type=controller`, `deployment.mode=multi`, `database.mode=cloudnativepg`, and NetworkPolicy rendering enabled. Gerbil `startupMode` supports `normal`, `delayed`, and `disabledUntilSetup`. ([GitHub][1])
### Step 4: Install Pangolin
Add the Helm repository:
```bash
helm repo add fossorial https://charts.fossorial.io
helm repo update fossorial
```
Install Pangolin:
```bash
helm upgrade --install pangolin fossorial/pangolin \
--namespace pangolin \
--values values-pangolin.yaml
```
Do not use `--create-namespace` here. The namespace was created and labeled before installation.
### Step 5: Verify the deployment
Check Helm release status:
```bash
helm status pangolin --namespace pangolin
helm history pangolin --namespace pangolin
```
Check workloads:
```bash
kubectl get pods --namespace pangolin
kubectl get deploy,statefulset --namespace pangolin
```
Check Services:
```bash
kubectl get svc --namespace pangolin
```
Check Traefik `IngressRoute` resources:
```bash
kubectl get ingressroute --namespace pangolin
```
If Traefik CRDs are not installed, this command will fail. In that case, install Traefik CRDs or enable/install the Traefik controller path required by your selected deployment mode.
Wait for the Pangolin pod to become ready:
```bash
kubectl wait --for=condition=ready pod \
-l app.kubernetes.io/name=pangolin \
--namespace pangolin \
--timeout=300s
```
## Accessing the dashboard
After DNS and Traefik routing are configured, access Pangolin through the dashboard URL:
```text
https://pangolin.example.com
```
The API route is exposed under:
```text
https://pangolin.example.com/api/v1
```
<Tip>
For a temporary local check, port-forward the dashboard/UI port:
```bash
kubectl port-forward --namespace pangolin svc/pangolin 8080:3002
```
Then open:
```text
http://localhost:8080
```
</Tip>
The chart routes `/api/v1` to the Pangolin external/API port and the dashboard route to the Next/UI port. The default service ports are `3000` for external/API and `3002` for the dashboard/UI. ([GitHub][1])
## Switch Gerbil to normal startup
If you installed with `gerbil.startupMode=delayed`, switch Gerbil to normal mode after the initial setup is complete:
```bash
helm upgrade pangolin fossorial/pangolin \
--namespace pangolin \
--reuse-values \
--set gerbil.startupMode=normal
```
Check Gerbil resources:
```bash
kubectl get pods,svc,pvc --namespace pangolin \
-l app.kubernetes.io/name=gerbil
```
## Upgrade
Update the Helm repository:
```bash
helm repo update fossorial
```
Upgrade the release:
```bash
helm upgrade pangolin fossorial/pangolin \
--namespace pangolin \
--values values-pangolin.yaml
```
Check upgrade status:
```bash
helm status pangolin --namespace pangolin
helm history pangolin --namespace pangolin
```
Rollback if needed:
```bash
helm rollback pangolin <revision> --namespace pangolin
```
## OCI install
The Pangolin chart is also published as an OCI chart in GHCR.
Pull the chart:
```bash
helm pull oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0
```
Install from OCI:
```bash
helm upgrade --install pangolin oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0 \
--namespace pangolin \
--values values-pangolin.yaml
```
OCI changes where Helm pulls the chart from. It does not change the values file or the release behavior.
## Architecture overview
Recommended deployment mode:
```yaml
deployment:
type: controller
mode: multi
```
In this topology:
| Component | Role |
| -------------------------- | -------------------------------------------------------------------- |
| Pangolin | Main application, dashboard, API, authentication, and configuration. |
| pangolin-kube-controller | Reconciles dynamic Kubernetes and Traefik CRD configuration. |
| Gerbil | WireGuard tunnel manager for Pangolin sites. |
| Traefik | Routes dashboard, API, and site traffic. |
| CloudNativePG / PostgreSQL | Stores Pangolin application state. |
Database modes:
| Mode | Use case |
| --------------- | --------------------------------------------------- |
| `cloudnativepg` | Recommended Kubernetes production path. |
| `external` | Production path with externally managed PostgreSQL. |
| `embedded` | Lab or test setups. |
| `sqlite` | Development or CI only. |
The chart supports `cloudnativepg`, `external`, `embedded`, and `sqlite` database modes. The chart comments mark `cloudnativepg` as the preferred production mode and SQLite as development/test only. ([GitHub][1])
## Chart signing
The chart metadata includes Artifact Hub signing information:
```text
Fingerprint: 48E7F670FCC13645FC48B08D587294B228C2EC2C
Public key: https://charts.fossorial.io/pgp_keys.asc
```
Use this metadata when verifying signed chart releases. The signing key and fingerprint are published in the chart annotations. ([GitHub][2])
## References
<CardGroup cols={2}>
<Card title="Chart README" href="https://github.com/fosrl/helm-charts/blob/main/charts/pangolin/README.md" icon="book" />
<Card title="values.yaml" href="https://github.com/fosrl/helm-charts/blob/main/charts/pangolin/values.yaml" icon="file-code" />
<Card title="values.schema.json" href="https://github.com/fosrl/helm-charts/blob/main/charts/pangolin/values.schema.json" icon="file-code" />
<Card title="Examples" href="https://github.com/fosrl/helm-charts/tree/main/charts/pangolin/examples" icon="list-check" />
<Card title="Issues" href="https://github.com/fosrl/helm-charts/issues" icon="circle-question" />
</CardGroup>
## Next steps
<CardGroup cols={2}>
<Card title="Full Configuration" href="/self-host/manual/kubernetes/pangolin/configuration" icon="sliders">
Review Pangolin chart options.
</Card>
<Card title="Troubleshooting" href="/self-host/manual/kubernetes/pangolin/troubleshooting" icon="circle-question">
Debug Pangolin deployment and routing issues.
</Card>
<Card title="Kustomize Install" href="/self-host/manual/kubernetes/pangolin/kustomize" icon="layer-group">
Install Pangolin with rendered manifests and Kustomize overlays.
</Card>
<Card title="GitOps" href="/self-host/manual/kubernetes/gitops/overview" icon="code-branch">
Deploy Pangolin with Argo CD or Flux.
</Card>
</CardGroup>
@@ -0,0 +1,694 @@
---
title: "Kustomize"
description: "Deploy Pangolin on Kubernetes using Helm-rendered manifests and Kustomize overlays."
---
Use Kustomize when you want to manage Pangolin with rendered manifests, environment-specific overlays, and explicit patches in Git.
For Pangolin, the supported Kustomize workflow is:
1. Render the Pangolin Helm chart to manifests.
2. Use the rendered output as the Kustomize base.
3. Create overlays per environment.
4. Apply the overlay with `kubectl apply -k` or reconcile it with Argo CD or Flux.
## When to use Kustomize for Pangolin
Use Kustomize if you:
- want environment-specific overlays for dev, staging, or production
- need explicit patches committed to Git
- prefer reviewing rendered Kubernetes manifests before applying them
- use Argo CD or Flux with Kustomize sources
- want to customize Helm-rendered output without forking the chart
For a simpler single-environment setup, use [Pangolin Helm](/self-host/manual/kubernetes/pangolin/helm).
## Version context
This page is aligned with the Pangolin Helm chart `0.1.0-alpha.0`.
| Item | Value |
| --- | --- |
| Chart version | `0.1.0-alpha.0` |
| Pangolin app version | `1.18.2` |
| Kubernetes version | `>=1.30.14-0` |
| Gerbil image tag | `1.3.1` |
| pangolin-kube-controller image tag | `0.1.0-alpha.1` |
| Traefik image tag | `v3.6.15` |
## Supported approach
The Pangolin chart does not provide native Kustomize bases. Render the Helm chart first, then use Kustomize on the rendered manifests.
<Warning>
Do not manage the same Pangolin resources with both a live Helm release and Kustomize. Pick one ownership model per environment.
</Warning>
Recommended ownership model:
- Use Helm only to render the Pangolin chart.
- Use Kustomize, Argo CD, or Flux to apply and reconcile the rendered manifests.
- Re-render the base when upgrading the chart or changing Helm values.
## Example directory structure
```text
pangolin-deployment/
├── base/
│ ├── kustomization.yaml
│ └── pangolin.yaml
├── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── patches/
│ │ └── pangolin-resources.patch.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── patches/
│ │ └── pangolin-resources.patch.yaml
│ └── prod/
│ ├── kustomization.yaml
│ └── patches/
│ ├── pangolin-resources.patch.yaml
│ └── ingressroute-host.patch.yaml
└── values/
├── values-base.yaml
├── values-dev.yaml
├── values-staging.yaml
└── values-prod.yaml
```
## Step 1: Create the namespace
Create the namespace before applying rendered manifests:
```bash
kubectl create namespace pangolin
```
Gerbil requires `NET_ADMIN` for WireGuard interface management. If your cluster enforces Pod Security Admission, label the namespace before creating workloads:
```bash
kubectl label namespace pangolin \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/audit=restricted \
--overwrite
```
<Warning>
Do not use a restricted Pod Security profile for a namespace running Gerbil unless you have validated the selected chart mode. Gerbil requires `NET_ADMIN`.
</Warning>
## Step 2: Create the Pangolin app Secret
Create a Secret for `SERVER_SECRET`:
```bash
kubectl create secret generic pangolin-app-secret \
--namespace pangolin \
--from-literal=SERVER_SECRET='<strong-random-secret>'
```
Do not commit this Secret to Git.
## Step 3: Create base values
Create `values/values-base.yaml`:
```yaml
deployment:
type: controller
mode: multi
installTraefikController: false
database:
mode: cloudnativepg
cloudnativepg:
cluster:
name: pangolin-db
cnpg-operator:
enabled: true
cnpg-cluster:
enabled: true
fullnameOverride: pangolin-db
cluster:
instances: 1
storage:
size: 8Gi
pangolin:
secret:
existingSecretName: pangolin-app-secret
existingSecretKey: SERVER_SECRET
config:
app:
dashboard_url: https://pangolin.example.com
domains:
domain1:
base_domain: example.com
cert_resolver: letsencrypt
gerbil:
base_endpoint: vpn.example.com
start_port: 51820
clients_start_port: 21820
traefik:
enabled: true
http_entrypoint: web
https_entrypoint: websecure
cert_resolver: letsencrypt
ingressRoute:
dashboard:
enabled: true
host: pangolin.example.com
entryPoints:
- websecure
tls:
enabled: true
certResolver: letsencrypt
secretName: ""
gerbil:
enabled: true
startupMode: delayed
persistence:
enabled: true
size: 1Gi
```
Replace:
* `pangolin.example.com`
* `example.com`
* `vpn.example.com`
* TLS resolver names
* storage settings
<Note>
Use `gerbil.startupMode=delayed` for the first install if Gerbil should not start before the initial Pangolin setup is complete. Switch it to `normal` after setup.
</Note>
## Step 4: Render Pangolin to the base
Add and update the Helm repository:
```bash
helm repo add fossorial https://charts.fossorial.io
helm repo update fossorial
```
Create directories:
```bash
mkdir -p base overlays/dev/patches overlays/staging/patches overlays/prod/patches values
```
Render the Pangolin chart:
```bash
helm template pangolin fossorial/pangolin \
--namespace pangolin \
--values values/values-base.yaml \
> base/pangolin.yaml
```
You can also render from the GHCR OCI chart:
```bash
helm template pangolin oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0 \
--namespace pangolin \
--values values/values-base.yaml \
> base/pangolin.yaml
```
## Step 5: Create the base kustomization
```yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pangolin.yaml
```
<Note>
The namespace is already rendered by Helm through `--namespace pangolin`. You can also set `namespace: pangolin` in Kustomize, but avoid changing namespaces in overlays unless you have verified all rendered resources and references.
</Note>
## Step 6: Inspect rendered resource names
Before writing patches, inspect the generated resource names:
```bash
kustomize build base | grep -E "^(kind:| name:)"
```
Or list the main resource names with `yq`:
```bash
kustomize build base | yq '. | select(.kind == "Deployment" or .kind == "StatefulSet" or .kind == "IngressRoute" or .kind == "Service") | .kind + " " + .metadata.name'
```
<Warning>
Do not assume generated resource names. Helm names can change with the release name, chart name, `nameOverride`, or `fullnameOverride`.
</Warning>
Use the actual rendered names in your patch targets.
## Step 7: Create a production overlay
Example `overlays/prod/kustomization.yaml`:
```yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
labels:
- pairs:
app.kubernetes.io/environment: production
app.kubernetes.io/managed-by: kustomize
patches:
- path: patches/pangolin-resources.patch.yaml
target:
group: apps
version: v1
kind: Deployment
name: pangolin
- path: patches/ingressroute-host.patch.yaml
target:
group: traefik.io
version: v1alpha1
kind: IngressRoute
name: pangolin-dashboard
```
<Note>
Replace `pangolin` and `pangolin-dashboard` with the actual names from your rendered manifests.
</Note>
## Step 8: Add patches
### Patch Pangolin resources
```yaml
# overlays/prod/patches/pangolin-resources.patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pangolin
spec:
template:
spec:
containers:
- name: pangolin
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
memory: 1Gi
```
<Note>
CPU limits are rendered by default through the chart's `resourcesPolicy.cpuLimits.enabled=true`. If you disable CPU limits in chart values, keep your Kustomize patches consistent with that policy.
</Note>
### Patch dashboard IngressRoute host
The Pangolin chart uses Traefik `IngressRoute` for the dashboard and API in controller mode, not a standard Kubernetes `Ingress`.
```yaml
# overlays/prod/patches/ingressroute-host.patch.yaml
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: pangolin-dashboard
spec:
routes:
- kind: Rule
match: Host(`pangolin-prod.example.com`) && PathPrefix(`/api/v1`)
- kind: Rule
match: Host(`pangolin-prod.example.com`)
```
<Warning>
Patch the rendered `IngressRoute` only after checking the route order and match rules. The API route and dashboard route target different service ports.
</Warning>
### Patch node affinity
```yaml
# overlays/prod/patches/pangolin-node-affinity.patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: pangolin
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-type
operator: In
values:
- production
```
Reference it in `overlays/prod/kustomization.yaml`:
```yaml
patches:
- path: patches/pangolin-node-affinity.patch.yaml
target:
group: apps
version: v1
kind: Deployment
name: pangolin
```
### Patch Gerbil startup mode
For first install, this should usually be handled in Helm values before rendering. If you still need to patch rendered manifests, inspect the generated Deployment first.
To switch Gerbil from delayed to normal mode, prefer updating values and re-rendering:
```yaml
gerbil:
startupMode: normal
```
Then re-render:
```bash
helm template pangolin fossorial/pangolin \
--namespace pangolin \
--values values/values-base.yaml \
> base/pangolin.yaml
```
## Do not rename rendered Helm resources by default
Avoid Kustomize options such as `namePrefix` and `nameSuffix` for Helm-rendered bases unless you have verified every generated reference.
Renaming rendered resources can break:
* Service selectors
* Secret references
* ConfigMap references
* ServiceAccount references
* NetworkPolicy selectors
* Traefik `IngressRoute` service references
* Prometheus monitor selectors
* CloudNativePG references
If you need different resource names, prefer changing the Helm release name or chart naming values before rendering.
## Apply the overlay
Preview the rendered output:
```bash
kustomize build overlays/prod
```
Compare with the live cluster:
```bash
kustomize build overlays/prod | kubectl diff -f -
```
Apply the overlay:
```bash
kubectl apply -k overlays/prod
```
Verify workloads:
```bash
kubectl get pods --namespace pangolin
kubectl get deploy,statefulset --namespace pangolin
kubectl get svc --namespace pangolin
```
Verify Traefik resources:
```bash
kubectl get ingressroute --namespace pangolin
```
Check events:
```bash
kubectl get events --namespace pangolin --sort-by=.lastTimestamp
```
## Updating the rendered base
When upgrading the Pangolin chart or changing Helm values, re-render the base and review the changes.
Update the Helm repository:
```bash
helm repo update fossorial
```
Render the updated chart output:
```bash
helm template pangolin fossorial/pangolin \
--namespace pangolin \
--values values/values-base.yaml \
> base/pangolin.yaml
```
Or with OCI:
```bash
helm template pangolin oci://ghcr.io/fosrl/helm-charts/pangolin \
--version 0.1.0-alpha.0 \
--namespace pangolin \
--values values/values-base.yaml \
> base/pangolin.yaml
```
Validate the overlay:
```bash
kustomize build overlays/prod
```
Review the diff:
```bash
git diff
kustomize build overlays/prod | kubectl diff -f -
```
Commit the updated base and overlays:
```bash
git add base/ overlays/ values/
git commit -m "Update Pangolin rendered manifests"
```
Apply after review:
```bash
kubectl apply -k overlays/prod
```
## Ownership model
Do not run `helm upgrade` against a release that is managed by Kustomize.
Avoid this pattern:
```bash
helm upgrade pangolin fossorial/pangolin --namespace pangolin
kubectl apply -k overlays/prod
```
Use one of these models instead:
| Model | Description |
| ----------------- | ------------------------------------------------------------------------------------------ |
| Helm-managed | Helm installs and upgrades the live release. Kustomize is not used for the same resources. |
| Kustomize-managed | Helm renders manifests only. Kustomize applies and owns the live resources. |
| GitOps-managed | Argo CD or Flux applies the Kustomize overlay and owns reconciliation. |
## Common Kustomize patches for Pangolin
### Patch resource requests and limits
```yaml
patches:
- path: patches/pangolin-resources.patch.yaml
target:
group: apps
version: v1
kind: Deployment
name: pangolin
```
### Patch IngressRoute host
```yaml
patches:
- path: patches/ingressroute-host.patch.yaml
target:
group: traefik.io
version: v1alpha1
kind: IngressRoute
name: pangolin-dashboard
```
### Add annotations
```yaml
patches:
- target:
group: apps
version: v1
kind: Deployment
name: pangolin
patch: |-
- op: add
path: /metadata/annotations
value:
example.com/owner: platform
```
### Patch Gerbil Service type
Patch the Gerbil Service only after checking the rendered Service name.
```yaml
patches:
- target:
version: v1
kind: Service
name: pangolin-gerbil
patch: |-
- op: replace
path: /spec/type
value: LoadBalancer
```
<Note>
For important topology settings such as database mode, Gerbil ports, `startupMode`, Traefik mode, and CloudNativePG settings, prefer changing Helm values and re-rendering instead of patching rendered YAML.
</Note>
## Validation
Validate Kustomize output:
```bash
kustomize build overlays/prod
```
Run a server-side dry run:
```bash
kustomize build overlays/prod | kubectl apply -f - --dry-run=server
```
Preview live changes:
```bash
kustomize build overlays/prod | kubectl diff -f -
```
If a patch does not apply, inspect generated resource names:
```bash
kustomize build base | grep -E "^(kind:| name:)"
```
## Troubleshooting
### The patch does not apply
Check the rendered resource name and kind:
```bash
kustomize build base | grep -E "^(kind:| name:)"
```
Then verify the patch target in your overlay.
### The pod does not start
Check pod status and events:
```bash
kubectl get pods --namespace pangolin
kubectl describe pod <pod-name> --namespace pangolin
kubectl get events --namespace pangolin --sort-by=.lastTimestamp
```
### Dashboard routing does not work
Check the rendered and applied `IngressRoute`:
```bash
kubectl get ingressroute --namespace pangolin
kubectl describe ingressroute <name> --namespace pangolin
```
Verify:
* Traefik CRDs are installed.
* A Traefik controller is watching the namespace and labels.
* `pangolin.ingressRoute.dashboard.host` or the patched host matches DNS.
* The API route still contains `PathPrefix(/api/v1)`.
* TLS settings match your Traefik setup.
### Gerbil does not start
Check Gerbil resources:
```bash
kubectl get pods,svc,pvc --namespace pangolin \
-l app.kubernetes.io/name=gerbil
```
Verify:
* namespace allows `NET_ADMIN`
* `gerbil.startupMode` is set correctly
* Gerbil persistence is enabled or intentionally disabled
* `pangolin.config.gerbil.start_port` matches `gerbil.ports.wg1`
* `pangolin.config.gerbil.clients_start_port` matches `gerbil.ports.wg2`
## Next steps
<CardGroup cols={2}>
<Card title="Helm Install" href="/self-host/manual/kubernetes/pangolin/helm" icon="box">
Install Pangolin with Helm.
</Card>
<Card title="Configuration" href="/self-host/manual/kubernetes/pangolin/configuration" icon="sliders">
Review Pangolin chart options.
</Card>
<Card title="Troubleshooting" href="/self-host/manual/kubernetes/pangolin/troubleshooting" icon="circle-question">
Debug Pangolin deployment and routing issues.
</Card>
<Card title="GitOps" href="/self-host/manual/kubernetes/gitops/overview" icon="code-branch">
Deploy Pangolin with Argo CD or Flux.
</Card>
</CardGroup>
@@ -0,0 +1,924 @@
---
title: "Troubleshooting"
description: "Diagnose and resolve Pangolin Kubernetes deployment issues."
---
This page covers troubleshooting Pangolin Kubernetes deployments across Helm, Kustomize, Argo CD, and Flux workflows.
Start with the core checks, then use the section that matches the symptom.
## Core diagnostics
Set the namespace and release name used by your installation:
```bash
export PANGOLIN_NAMESPACE=pangolin
export PANGOLIN_RELEASE=pangolin
```
### Helm diagnostics
Check the release:
```bash
helm status "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE"
helm history "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE"
helm get values "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE" --all
```
Render the chart locally with your values file:
```bash
helm repo update fossorial
helm template "$PANGOLIN_RELEASE" fossorial/pangolin \
--namespace "$PANGOLIN_NAMESPACE" \
--values values-pangolin.yaml
```
Preview an upgrade:
```bash
helm upgrade "$PANGOLIN_RELEASE" fossorial/pangolin \
--namespace "$PANGOLIN_NAMESPACE" \
--values values-pangolin.yaml \
--dry-run
```
<Note>
`helm lint charts/pangolin` is only useful when you are working inside the Helm chart repository. For normal installs, use `helm template` and `helm upgrade --dry-run`.
</Note>
### Kubernetes diagnostics
Check workloads and events:
```bash
kubectl get pods --namespace "$PANGOLIN_NAMESPACE"
kubectl get deploy,statefulset,job,cronjob --namespace "$PANGOLIN_NAMESPACE"
kubectl get events --namespace "$PANGOLIN_NAMESPACE" --sort-by=.lastTimestamp
```
Inspect a pod:
```bash
kubectl describe pod <pod-name> --namespace "$PANGOLIN_NAMESPACE"
kubectl logs <pod-name> --namespace "$PANGOLIN_NAMESPACE" --all-containers --tail=200
```
Check services, PVCs, and policies:
```bash
kubectl get svc,pvc,secret,configmap --namespace "$PANGOLIN_NAMESPACE"
kubectl get networkpolicy --namespace "$PANGOLIN_NAMESPACE"
```
### Traefik diagnostics
In controller mode, the chart uses Traefik CRDs such as `IngressRoute`.
Check whether Traefik CRDs are installed:
```bash
kubectl get crd | grep traefik
```
Check rendered or applied Traefik resources:
```bash
kubectl get ingressroute --namespace "$PANGOLIN_NAMESPACE"
kubectl describe ingressroute <name> --namespace "$PANGOLIN_NAMESPACE"
```
Depending on your Traefik setup, also check:
```bash
kubectl get middleware,tlsoption,traefikservice --namespace "$PANGOLIN_NAMESPACE"
```
<Note>
`kubectl get ingress` is only useful if your selected deployment mode renders standard Kubernetes Ingress resources. In controller mode, use `IngressRoute`.
</Note>
### Database diagnostics
If you use CloudNativePG, first check that the CRD exists:
```bash
kubectl get crd | grep postgresql.cnpg.io
```
Then check CNPG resources:
```bash
kubectl get cluster --namespace "$PANGOLIN_NAMESPACE"
kubectl describe cluster <cluster-name> --namespace "$PANGOLIN_NAMESPACE"
kubectl get pods --namespace "$PANGOLIN_NAMESPACE" | grep -E 'pangolin-db|postgres'
kubectl get secret --namespace "$PANGOLIN_NAMESPACE" | grep -E 'pangolin-db|postgres'
```
If you use external PostgreSQL, verify the connection Secret:
```bash
kubectl get secret <connection-secret-name> --namespace "$PANGOLIN_NAMESPACE"
kubectl describe secret <connection-secret-name> --namespace "$PANGOLIN_NAMESPACE"
```
Do not decode and paste database credentials into logs, screenshots, or issue reports.
## Common issues and solutions
<AccordionGroup>
<Accordion title="Gerbil fails during the first install">
**Symptoms**
* Gerbil pod crashes during a fresh install.
* Logs mention missing setup data, missing exit node, or tunnel configuration not being ready.
* Pangolin itself is not initialized yet.
**Cause**
On first install, Gerbil may start before Pangolin has completed the initial setup. The chart supports `gerbil.startupMode` for this case.
**Resolution**
Use delayed startup for the first install:
```yaml
gerbil:
startupMode: delayed
```
Install or upgrade with the values file:
```bash
helm upgrade --install "$PANGOLIN_RELEASE" fossorial/pangolin \
--namespace "$PANGOLIN_NAMESPACE" \
--values values-pangolin.yaml
```
After Pangolin setup is complete, switch Gerbil to normal startup:
```bash
helm upgrade "$PANGOLIN_RELEASE" fossorial/pangolin \
--namespace "$PANGOLIN_NAMESPACE" \
--reuse-values \
--set gerbil.startupMode=normal
```
Check Gerbil resources:
```bash
kubectl get pods,svc,pvc --namespace "$PANGOLIN_NAMESPACE" \
-l app.kubernetes.io/name=gerbil
```
</Accordion>
<Accordion title="Gerbil is blocked by Pod Security Admission">
**Symptoms**
* Gerbil pod does not start.
* Events mention Pod Security Admission.
* Events mention forbidden capabilities.
* Logs or events mention `NET_ADMIN`.
**Cause**
Gerbil requires the `NET_ADMIN` Linux capability for WireGuard interface management. A namespace using a restricted Pod Security profile can block this.
**Resolution**
Check namespace labels:
```bash
kubectl get namespace "$PANGOLIN_NAMESPACE" --show-labels
```
For a namespace running Gerbil, use a policy profile that allows the required capability. Example:
```bash
kubectl label namespace "$PANGOLIN_NAMESPACE" \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/warn=baseline \
pod-security.kubernetes.io/audit=restricted \
--overwrite
```
Then restart the affected pods:
```bash
kubectl rollout restart deploy --namespace "$PANGOLIN_NAMESPACE"
```
<Warning>
Do not use a restricted Pod Security profile for Gerbil unless you have validated the selected chart mode and security context. Removing `NET_ADMIN` breaks WireGuard management.
</Warning>
</Accordion>
<Accordion title="Dashboard is not reachable through IngressRoute">
**Symptoms**
* The dashboard URL does not load.
* Browser shows timeout, bad gateway, 404, or TLS error.
* API path `/api/v1` fails while the dashboard path works, or the reverse.
**Common causes**
* DNS points to the wrong load balancer or ingress endpoint.
* Traefik CRDs are missing.
* Traefik controller is not watching the namespace or selector labels.
* `IngressRoute` host does not match the dashboard URL.
* API route was changed and no longer matches `PathPrefix(/api/v1)`.
* TLS resolver or TLS Secret is misconfigured.
**Checks**
Check DNS:
```bash
nslookup pangolin.example.com
```
Check Traefik CRDs:
```bash
kubectl get crd | grep traefik
```
Check IngressRoute resources:
```bash
kubectl get ingressroute --namespace "$PANGOLIN_NAMESPACE"
kubectl describe ingressroute <name> --namespace "$PANGOLIN_NAMESPACE"
```
Check the rendered values:
```bash
helm get values "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE" --all | grep -A30 ingressRoute
```
Check Traefik logs. Adjust the namespace and label selector to your Traefik installation:
```bash
kubectl logs --namespace traefik -l app.kubernetes.io/name=traefik --tail=100
```
Temporary local check for the dashboard/UI service port:
```bash
kubectl port-forward --namespace "$PANGOLIN_NAMESPACE" svc/pangolin 8080:3002
```
Then open:
```text
http://localhost:8080
```
<Note>
The dashboard/UI port is `3002`. The API/external port is `3000`. Port-forward `3002` when checking the dashboard locally.
</Note>
</Accordion>
<Accordion title="TLS certResolver and secretName conflict">
**Symptoms**
* `IngressRoute` is created but TLS does not work.
* Traefik logs mention TLS configuration problems.
* Certificate is not issued or the TLS Secret is not found.
**Cause**
The dashboard `IngressRoute` TLS configuration should use either a Traefik certificate resolver or an existing TLS Secret.
**Resolution**
Use Traefik ACME certificate resolver:
```yaml
pangolin:
ingressRoute:
dashboard:
tls:
enabled: true
certResolver: letsencrypt
secretName: ""
```
Or use an existing TLS Secret:
```yaml
pangolin:
ingressRoute:
dashboard:
tls:
enabled: true
certResolver: ""
secretName: pangolin-dashboard-tls
```
Verify the Secret if using `secretName`:
```bash
kubectl get secret pangolin-dashboard-tls --namespace "$PANGOLIN_NAMESPACE"
```
<Note>
`certResolver` is a Traefik ACME resolver setting. It is not a cert-manager issuer reference.
</Note>
</Accordion>
<Accordion title="Newt cannot reach Gerbil WireGuard ports">
**Symptoms**
* Newt shows repeated connection or tunnel errors.
* Tunnel traffic does not pass.
* WireGuard UDP ports are unreachable from the Newt location.
**Common causes**
* `pangolin.config.gerbil.base_endpoint` points to the wrong host.
* Gerbil Service is not exposed as expected.
* External firewall blocks UDP traffic.
* NetworkPolicy blocks the required traffic.
* `pangolin.config.gerbil.start_port` and `gerbil.ports.wg1` are not aligned.
* `pangolin.config.gerbil.clients_start_port` and `gerbil.ports.wg2` are not aligned.
**Checks**
Check Gerbil Service:
```bash
kubectl get svc --namespace "$PANGOLIN_NAMESPACE" \
-l app.kubernetes.io/name=gerbil
kubectl describe svc <gerbil-service-name> --namespace "$PANGOLIN_NAMESPACE"
```
Check Gerbil values:
```bash
helm get values "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE" --all | grep -A30 gerbil
```
Check NetworkPolicies:
```bash
kubectl get networkpolicy --namespace "$PANGOLIN_NAMESPACE"
kubectl describe networkpolicy --namespace "$PANGOLIN_NAMESPACE"
```
Verify external firewall rules for the configured UDP ports.
</Accordion>
<Accordion title="Gerbil is behind reverse proxy or UDP gateway and tunnels fail">
**Symptoms**
* Newt peers do not establish stable handshakes.
* Tunnel traffic drops even though Gerbil pods are healthy.
* Logs show connection resets or malformed upstream traffic.
**Cause**
Proxy protocol handling is inconsistent between the upstream hop and Gerbil.
<Info>
If Gerbil is exposed through a reverse proxy or UDP gateway, keep proxy protocol settings aligned end-to-end. Do not enable proxy protocol on the upstream hop unless Gerbil is configured to accept it.
</Info>
**Checks**
Check endpoint and port alignment:
```bash
helm get values "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE" --all | grep -A40 gerbil
```
Check Gerbil logs:
```bash
kubectl logs --namespace "$PANGOLIN_NAMESPACE" \
-l app.kubernetes.io/name=gerbil \
--tail=200
```
Check Service exposure:
```bash
kubectl get svc --namespace "$PANGOLIN_NAMESPACE" \
-l app.kubernetes.io/name=gerbil -o wide
```
</Accordion>
<Accordion title="External database mode missing or invalid Secret">
**Symptoms**
* Pangolin pod crashes.
* Logs mention database connection errors.
* Events mention missing Secret or missing Secret key.
**Cause**
`database.mode=external` needs a valid database connection Secret unless the chart is configured to generate one from values.
**Resolution**
Create a connection Secret:
```bash
kubectl create secret generic pangolin-db-connection \
--namespace "$PANGOLIN_NAMESPACE" \
--from-literal=connectionString='postgresql://pangolin:password@postgres.example.com:5432/pangolin?sslmode=require'
```
Reference it in values:
```yaml
database:
mode: external
connection:
existingSecretName: pangolin-db-connection
existingSecretKey: connectionString
```
Check the Secret:
```bash
kubectl describe secret pangolin-db-connection --namespace "$PANGOLIN_NAMESPACE"
```
<Warning>
Do not put database passwords directly in values files for production. Use an existing Secret or your normal secret-management workflow.
</Warning>
</Accordion>
<Accordion title="CloudNativePG cluster does not provision">
**Symptoms**
* CNPG Cluster resource is missing.
* CNPG pods do not start.
* Pangolin cannot connect to the generated CNPG database.
* Secret such as `pangolin-db-app` is missing.
**Common causes**
* CloudNativePG CRDs/operator are not installed.
* `cnpg-cluster.enabled` is false when you expected the chart to create a cluster.
* `cnpg-operator.enabled` is false and no operator exists.
* `database.cloudnativepg.cluster.name` does not match the CNPG cluster name.
* StorageClass or PVC provisioning fails.
**Checks**
Check CRDs:
```bash
kubectl get crd | grep postgresql.cnpg.io
```
Check CNPG operator pods:
```bash
kubectl get pods --all-namespaces | grep -i cnpg
```
Check CNPG Cluster:
```bash
kubectl get cluster --namespace "$PANGOLIN_NAMESPACE"
kubectl describe cluster pangolin-db --namespace "$PANGOLIN_NAMESPACE"
```
Check PVCs and Secrets:
```bash
kubectl get pvc --namespace "$PANGOLIN_NAMESPACE"
kubectl get secret --namespace "$PANGOLIN_NAMESPACE" | grep pangolin-db
```
Expected naming when using the default example:
```yaml
database:
cloudnativepg:
cluster:
name: pangolin-db
cnpg-cluster:
enabled: true
fullnameOverride: pangolin-db
```
</Accordion>
<Accordion title="NetworkPolicy blocks DNS, database, controller, or tunnel traffic">
**Symptoms**
* DNS lookups fail.
* Pangolin cannot connect to the database.
* Controller cannot reach the Kubernetes API.
* Gerbil or Newt traffic does not work.
* External services such as SMTP, OIDC, or webhooks time out.
**Cause**
The chart can render NetworkPolicies. If your CNI enforces them, missing egress or ingress rules can break required paths.
**Checks**
```bash
kubectl get networkpolicy --namespace "$PANGOLIN_NAMESPACE"
kubectl describe networkpolicy --namespace "$PANGOLIN_NAMESPACE"
```
Check whether DNS is allowed:
```yaml
networkPolicy:
dns:
enabled: true
```
Check database egress:
```yaml
networkPolicy:
database:
enabled: true
port: 5432
```
Check controller API access:
```yaml
networkPolicy:
controller:
egress:
enabled: true
kubernetesApi:
enabled: true
port: 443
```
For external integrations, add scoped egress rules for the required services instead of allowing broad egress.
For a temporary isolation test, disable NetworkPolicy and re-apply:
```yaml
networkPolicy:
enabled: false
```
If this fixes the issue, re-enable policies and add the missing rules.
</Accordion>
<Accordion title="Pangolin pod is CrashLoopBackOff or Pending">
**Symptoms**
* Pangolin pod restarts repeatedly.
* Pod stays Pending.
* Readiness never becomes true.
**Checks**
Find the pod:
```bash
kubectl get pods --namespace "$PANGOLIN_NAMESPACE" \
-l app.kubernetes.io/name=pangolin
```
Inspect it:
```bash
kubectl describe pod <pod-name> --namespace "$PANGOLIN_NAMESPACE"
kubectl logs <pod-name> --namespace "$PANGOLIN_NAMESPACE" --tail=200
kubectl logs <pod-name> --namespace "$PANGOLIN_NAMESPACE" --previous --tail=200
```
Check PVCs:
```bash
kubectl get pvc --namespace "$PANGOLIN_NAMESPACE"
kubectl describe pvc <pvc-name> --namespace "$PANGOLIN_NAMESPACE"
```
Common causes:
| Status | Common causes |
| ------------------ | ----------------------------------------------------------------------------------------------------- |
| `CrashLoopBackOff` | Database connection issue, missing Secret, invalid config, startup dependency not ready |
| `Pending` | PVC not bound, insufficient resources, node selector/affinity mismatch, Pod Security policy rejection |
| `ImagePullBackOff` | Wrong image override, registry access issue, missing imagePullSecret |
<Note>
Do not assume tools such as `psql`, `curl`, or `dig` are available inside the Pangolin container. Use logs, Events, or a temporary debug pod when needed.
</Note>
Run a temporary debug pod for network tests:
```bash
kubectl run net-debug \
--namespace "$PANGOLIN_NAMESPACE" \
--rm -it \
--image=curlimages/curl:latest \
--restart=Never \
-- sh
```
</Accordion>
<Accordion title="Traefik CRDs or resources are missing">
**Symptoms**
* Helm template or install succeeds, but Traefik resources are not reconciled.
* `kubectl get ingressroute` fails with unknown resource type.
* Argo CD or Flux reports missing kind `IngressRoute`.
**Cause**
Controller mode expects Traefik CRDs and a Traefik controller. They must be installed separately or through the bundled dependency when enabled.
**Checks**
```bash
kubectl get crd | grep traefik
kubectl get pods --all-namespaces | grep -i traefik
```
If you want the chart to install the bundled Traefik controller, enable it:
```yaml
deployment:
type: controller
installTraefikController: true
```
If Traefik is already installed elsewhere, keep it disabled and make sure the controller watches the namespace and labels used by the Pangolin `IngressRoute`.
</Accordion>
<Accordion title="Helm upgrade fails or rendered output is unexpected">
**Symptoms**
* `helm upgrade` fails.
* Rendered resources changed unexpectedly.
* Existing resources conflict with chart-managed resources.
* GitOps reports immutable field changes or ownership conflicts.
**Checks**
Render before upgrading:
```bash
helm template "$PANGOLIN_RELEASE" fossorial/pangolin \
--namespace "$PANGOLIN_NAMESPACE" \
--values values-pangolin.yaml > rendered.yaml
```
Run a server-side dry run:
```bash
kubectl apply -f rendered.yaml --dry-run=server
```
Compare the current live release:
```bash
helm get manifest "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE" > live-release.yaml
diff -u live-release.yaml rendered.yaml
```
Check ownership conflicts:
```bash
kubectl get all --namespace "$PANGOLIN_NAMESPACE" -o yaml | grep -E "meta.helm.sh|app.kubernetes.io/managed-by"
```
Avoid `--force` unless you understand which resources will be recreated.
<Warning>
`helm upgrade --force` can delete and recreate resources. That can interrupt traffic and may affect persistent workloads depending on the resource type.
</Warning>
</Accordion>
<Accordion title="Kustomize patches do not apply">
**Symptoms**
* Kustomize build succeeds but changes are missing.
* Patch target does not match any resource.
* Patch breaks after chart upgrade.
**Checks**
List generated resource names:
```bash
kustomize build base | grep -E "^(kind:| name:)"
```
Validate the overlay:
```bash
kustomize build overlays/prod
```
Run a server-side dry run:
```bash
kustomize build overlays/prod | kubectl apply -f - --dry-run=server
```
Preview live changes:
```bash
kustomize build overlays/prod | kubectl diff -f -
```
Use modern Kustomize `patches` syntax:
```yaml
patches:
- path: patches/pangolin-resources.patch.yaml
target:
group: apps
version: v1
kind: Deployment
name: pangolin
```
<Note>
For Helm-rendered bases, do not assume resource names. Check the rendered manifests after each chart upgrade.
</Note>
</Accordion>
<Accordion title="GitOps deployment does not sync">
**Symptoms**
* Argo CD Application is OutOfSync or Degraded.
* Flux HelmRelease or Kustomization is not Ready.
* Resources are missing or constantly reverted.
**Argo CD checks**
```bash
kubectl describe application pangolin --namespace argocd
kubectl logs --namespace argocd deployment/argocd-application-controller --tail=100
argocd app diff pangolin
argocd app sync pangolin
```
**Flux checks**
```bash
flux get sources all --all-namespaces
flux get helmreleases --all-namespaces
flux get kustomizations --all-namespaces
flux logs --all-namespaces --follow
```
Reconcile manually:
```bash
flux reconcile helmrelease pangolin --namespace "$PANGOLIN_NAMESPACE"
flux reconcile kustomization pangolin --namespace flux-system
```
Common causes:
- chart repository or OCI source not reachable
- wrong chart version
- missing CRDs
- invalid values
- rendered resource ownership conflict
- Secret not available in the expected namespace
</Accordion>
</AccordionGroup>
## Routing issues to the right repository
Use the repository that matches the failing area:
| Area | Repository |
| ----------------------------------------------------- | ------------------- |
| Chart templates, values, examples, rendered manifests | `fosrl/helm-charts` |
| Pangolin runtime, API, UI, auth, application behavior | `fosrl/pangolin` |
| Newt client behavior or connectivity | `fosrl/newt` |
| Documentation | `fosrl/docs-v2` |
## Before opening an issue, collect
Collect this information before opening an issue:
* chart version
* Pangolin app version
* Kubernetes version
* Helm version
* deployment method: Helm, Kustomize, Argo CD, or Flux
* sanitized values file
* pod logs
* namespace events
* Traefik logs, if routing is involved
* rendered manifests from `helm template` or `kustomize build`
* Helm release status or GitOps sync status
* reproduction steps
Collect basic diagnostics:
```bash
kubectl version
helm version
helm status "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE"
helm get values "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE" --all > pangolin-values.yaml
helm get manifest "$PANGOLIN_RELEASE" --namespace "$PANGOLIN_NAMESPACE" > pangolin-manifest.yaml
kubectl get pods --namespace "$PANGOLIN_NAMESPACE" -o wide > pangolin-pods.txt
kubectl get events --namespace "$PANGOLIN_NAMESPACE" --sort-by=.lastTimestamp > pangolin-events.txt
```
Before sharing diagnostics, remove:
* database passwords
* `SERVER_SECRET`
* API keys
* OAuth/OIDC client secrets
* TLS private keys
* internal hostnames, if sensitive
## Useful command reference
```bash
# General cluster info
kubectl cluster-info
kubectl version
# Namespace overview
kubectl get all --namespace "$PANGOLIN_NAMESPACE"
kubectl get pvc,secret,configmap --namespace "$PANGOLIN_NAMESPACE"
kubectl get events --namespace "$PANGOLIN_NAMESPACE" --sort-by=.lastTimestamp
# Logs
kubectl logs --namespace "$PANGOLIN_NAMESPACE" \
-l app.kubernetes.io/name=pangolin \
--tail=200
kubectl logs --namespace "$PANGOLIN_NAMESPACE" \
-l app.kubernetes.io/name=gerbil \
--tail=200
# Dashboard local test
kubectl port-forward --namespace "$PANGOLIN_NAMESPACE" svc/pangolin 8080:3002
# Traefik resources
kubectl get ingressroute --namespace "$PANGOLIN_NAMESPACE"
# Resource usage
kubectl top pod --namespace "$PANGOLIN_NAMESPACE"
kubectl top node
```
## Next steps
<CardGroup cols={2}>
<Card title="Pangolin Configuration" href="/self-host/manual/kubernetes/pangolin/configuration" icon="sliders">
Review Pangolin chart options.
</Card>
<Card title="Helm Quick-Start" href="/self-host/manual/kubernetes/pangolin/helm" icon="box">
Install Pangolin with Helm.
</Card>
<Card title="Kustomize Quick-Start" href="/self-host/manual/kubernetes/pangolin/kustomize" icon="layer-group">
Install Pangolin with rendered manifests and Kustomize overlays.
</Card>
<Card title="GitOps Overview" href="/self-host/manual/kubernetes/gitops/overview" icon="code-branch">
Deploy Pangolin with Argo CD or Flux.
</Card>
</CardGroup>
@@ -0,0 +1,221 @@
---
title: "Prerequisites"
description: "Cluster, tooling, networking, and storage requirements for deploying Pangolin and Sites (Newt) on Kubernetes."
---
Before installing Pangolin or Sites (Newt) on Kubernetes, check that your cluster, tools, networking, and storage setup match the deployment path you want to use.
## Kubernetes cluster
Use a Kubernetes version that satisfies the Helm chart `kubeVersion` requirement and is supported by your Kubernetes provider or distribution.
Check your cluster version:
```bash
kubectl version
```
<Info>
See the [Version Matrix](https://github.com/fosrl/helm-charts/VERSION_MATRIX.md) for the supported Kubernetes versions of the Pangolin and Newt Helm charts.
</Info>
## Controller access and RBAC
Controller mode is the default and recommended Kubernetes deployment mode for Pangolin.
When controller mode is enabled, the Pangolin Kube Controller runs with its own ServiceAccount and needs permission to watch and manage the Kubernetes and Traefik resources it reconciles. The chart creates the required RBAC resources for you, unless RBAC creation is disabled.
By default, the controller is scoped to the namespace of a single Pangolin deployment. It can also be configured for a broader scope when one controller should reconcile resources for multiple Pangolin deployments.
Depending on the configured controller scope, the controller needs namespace-scoped or cluster-scoped access to the resources it reconciles:
| API group | Resources | Verbs |
| --- | --- | --- |
| `""` | `events` | `create`, `patch`, `update` |
| `""` | `services`, `endpoints` | `get`, `list`, `watch`, `create`, `update`, `patch`, `delete` |
| `discovery.k8s.io` | `endpointslices` | `get`, `list`, `watch`, `create`, `update`, `patch`, `delete` |
| `traefik.io` | `ingressroutes`, `ingressroutetcps`, `ingressrouteudps`, `middlewares`, `middlewaretcps`, `traefikservices`, `serverstransports`, `serverstransporttcps`, `tlsoptions`, `tlsstores` | `get`, `list`, `watch`, `create`, `update`, `patch`, `delete` |
If leader election is enabled, the controller also needs access to:
| API group | Resources | Verbs |
| --- | --- | --- |
| `coordination.k8s.io` | `leases` | `get`, `list`, `watch`, `create`, `update`, `patch` |
The controller also needs cluster-wide read access to Kubernetes discovery resources:
| API group | Resources | Verbs |
| --- | --- | --- |
| `networking.k8s.io` | `ingressclasses` | `get`, `list`, `watch` |
| `apiextensions.k8s.io` | `customresourcedefinitions` | `get`, `list`, `watch` |
<Info>
For namespace-scoped deployments, the chart creates namespaced RBAC for the controller namespace and, if configured, the target namespace. For broader controller scopes, the chart creates the required cluster-scoped RBAC.
</Info>
## Database and storage
Pangolin requires a database backend. The Helm chart supports multiple database modes, including CloudNativePG, external PostgreSQL, embedded PostgreSQL, and SQLite.
For persistent database-backed deployments, make sure your cluster has a usable StorageClass or configure the StorageClass explicitly in your chart values.
Check available StorageClasses:
```bash
kubectl get storageclasses
```
For long-running/production deployments, prefer PostgreSQL-based modes such as CloudNativePG or external PostgreSQL.
<Info>
SQLite can be useful for simple or test deployments, but PostgreSQL-based modes are the better fit for long-running/production Kubernetes deployments.
</Info>
### Site connector storage
A Site (Newt) deployment does not require persistent storage by default.
Use writable configuration persistence only if your deployment needs runtime configuration to survive pod replacement, upgrades, node drains, or rescheduling. For simple deployments, no PVC is required.
## Networking
### Ingress and routing
Pangolin needs an external entrypoint for the dashboard, API, and site traffic.
Depending on your chart values, this can use:
* controller mode with a Traefik ingress controller
* standalone mode with chart-managed Traefik components
* an existing ingress or load balancer setup
If you use controller mode with Traefik CRDs, verify that the required Traefik API resources are available:
```bash
kubectl api-resources --api-group=traefik.io
```
You can also check existing ingress resources:
```bash
kubectl get ingress -A
```
### DNS
Configure DNS records for the domains used by Pangolin before exposing it publicly.
At minimum, the Pangolin dashboard domain should resolve to the ingress controller, load balancer, or public endpoint used by your deployment.
Example:
```bash
nslookup pangolin.example.com
```
For tunneled site deployments, also verify the DNS name used by the site connector endpoint.
### TLS
Use HTTPS for the Pangolin dashboard and API.
Common TLS options include:
* Traefik ACME / Let's Encrypt
* cert-manager
* a pre-created Kubernetes TLS Secret
* TLS termination at an external load balancer or ingress controller
Use the TLS method that matches your ingress and cluster setup.
If you use cert-manager, verify that the certificate CRDs are available:
```bash
kubectl get crd certificates.cert-manager.io
```
## Namespace and security
Choose the namespace where Pangolin and related components should run.
Example:
```bash
kubectl create namespace pangolin
```
When using Helm, you can also let Helm create the namespace:
```bash
helm upgrade --install pangolin fossorial/pangolin \
--namespace pangolin \
--create-namespace
```
If your cluster enforces Pod Security Admission, make sure the namespace labels match the selected deployment mode. Deployments that include tunnel components may require permissions that are not compatible with a fully restricted namespace profile.
## NetworkPolicy
The Pangolin and Newt charts can manage NetworkPolicies for the required application traffic.
If you enable chart-managed NetworkPolicies, review the generated policies before adding custom deny rules. If you replace them with your own policies, allow the required traffic between the components you deploy, such as Pangolin, Traefik, Gerbil, the database, DNS, and Site connectors.
## Resource planning
Pangolin and Site (Newt) Kubernetes deployments include predefined resource profiles for the supported deployment methods. These profiles set CPU and memory requests and limits for the components used by the selected deployment mode.
The available profiles are:
| Profile | Intended use |
| --- | --- |
| Small | Small deployments, or clusters with very limited available resources. |
| Standard | Default profile for most normal deployments. |
| Large | Larger environments with more Sites, more users, higher traffic, or stricter availability expectations. |
The selected profile applies to the workloads that are part of your deployment, for example:
| Component | Resource considerations |
| --- | --- |
| Pangolin | Main application workload. Size according to dashboard/API usage, users, and traffic. |
| Pangolin Kube Controller | Required in controller mode. Size according to the number of reconciled Kubernetes and Traefik resources. |
| Traefik | Size according to ingress and proxy traffic. |
| Gerbil | Required when the tunnel stack is enabled. Size according to tunnel traffic and number of connected Sites. |
| PostgreSQL / CloudNativePG | Size according to database mode, stored state, and expected write/read activity. |
| Site connectors (Newt) | Each Site connector adds its own resource usage. Size according to the traffic handled by that Site. |
<Info>
The Standard profile is intended to be enough for most standard deployments. Use Small for very limited lab or test environments, and Large for higher traffic, more Sites, more users, or larger production environments.
</Info>
After installation, monitor CPU and memory usage and adjust the selected profile or individual resource overrides if needed.
<Warning>
Avoid setting CPU limits on latency-sensitive Pangolin components unless your cluster policy requires them or you intentionally want to cap CPU usage.
CPU limits can cause throttling when a workload temporarily needs more CPU, even if spare CPU capacity is available on the node. This can negatively affect ingress, tunnel, proxy, database, and controller workloads.
For most deployments, use CPU requests to reserve baseline capacity and memory limits to protect the node from excessive memory usage.
</Warning>
## Next steps
<CardGroup cols={2}>
<Card title="Choose an Installation Path" href="/self-host/manual/kubernetes/choose-method" icon="route">
Pick the Kubernetes workflow that matches how you deploy applications.
</Card>
<Card title="Helm Quick-Start" href="/self-host/manual/kubernetes/helm" icon="box">
Install Pangolin or Sites (Newt) with Helm.
</Card>
<Card title="Kustomize Quick-Start" href="/self-host/manual/kubernetes/kustomize" icon="layer-group">
Use Kustomize overlays and patches.
</Card>
<Card title="Argo CD Guide" href="/self-host/manual/kubernetes/gitops/argocd" icon="code-branch">
Deploy Pangolin or Sites (Newt) with Argo CD.
</Card>
<Card title="Flux Guide" href="/self-host/manual/kubernetes/gitops/flux" icon="code-branch">
Deploy Pangolin or Sites (Newt) with Flux.
</Card>
<Card title="Pangolin Helm" href="/self-host/manual/kubernetes/pangolin/helm" icon="server">
Start with the Pangolin Helm installation guide.
</Card>
</CardGroup>
@@ -0,0 +1,238 @@
---
title: "Docker Compose"
description: "Deploy managed Pangolin manually using Docker Compose without the automated installer"
---
<Tip>
This guide is for managed self-hosted Pangolin. If you are looking for self-hosted Community Edition Pangolin please see this [Docker Compose](/self-host/manual/docker-compose) guide.
</Tip>
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.
## Prerequisites
Checkout the [quick install guide](self-host/quick-install) 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 (*)
```
<Info>
Files marked with `(*)` must be created manually. Volumes and other files are generated automatically by the services.
</Info>
<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
</Accordion>
<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
</Accordion>
<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>
<Steps>
<Step title="Create configuration directory">
```bash
mkdir -p config/traefik config/db
```
</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/config.yml`
</Step>
<Step title="Update domain">
Edit the configuration files to replace:
- `154.123.45.67` with your actual domain OR public IP address of the node
<Warning>
Ensure your domain DNS is properly configured to point to your server's IP address if you choose DNS.
</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>
</Steps>
## Docker Compose Configuration
Create `docker-compose.yml` in your project root:
```yaml title="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
- 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`:
```yaml title="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
```yaml title="config/config.yml"
gerbil:
start_port: 51820
base_endpoint: "154.123.45.67" # REPLACE WITH YOUR IP OR DOMAIN
managed:
id: "he4g78wevj25msf"
secret: "n7sd18twfko0q0vrb7wyclqzbvvnx1fqt7ezv8xewhdb9s7d"
```
@@ -0,0 +1,487 @@
---
title: "Podman Quadlets (Rootless)"
description: "Deploy Pangolin manually using Podman Quadlets (rootless) without the automated installer"
---
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 Podman installed and has been tested on Debian 13.5 with Podman version 5.4.2.
## 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>
<Warning>
## Note about ports 80 and 443
By default, unprivileged users cannot bind to privileged ports (< 1024). Some workarounds for this include:
1. Changing the unprivileged start port to 80
2. Using `iptables` / `nftables` to redirect 80 and 443 to ports above 1023 (such as 8080 and 8443, respectively)
Configuring this is out of the scope of this guide, but many guides exist online for this exact situation.
**This guide assumes you use option 1.**
</Warning>
## File Layout
Create the following project structure:
```text
~/.config/
└── containers/
└── systemd
├── gerbil.container
├── pangolin.container
├── traefik.container
├── services.pod
└── 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/containers/systemd # only needed if this is the first time you're running rootless Podman containers
cd ~/.config/containers/systemd
mkdir -p config/db config/letsencrypt config/traefik/logs
```
## Create the Configuration Files
<Steps>
<Step title="Create container and pod files">
This section defines the Pangolin, Gerbil, and Traefik containers, the pod, their shared volumes, and the ports exposed on the host.
```ini title="pangolin.container"
[Container]
ContainerName=pangolin
Image=docker.io/fosrl/pangolin:ee-latest
Pod=services.pod
HealthCmd=["curl","-f","http://localhost:3001/api/v1/"]
HealthInterval=10s
HealthRetries=15
HealthTimeout=10s
Notify=healthy
Volume=./config:/app/config
Volume=./config/letsencrypt:/app/config/letsencrypt:ro
[Service]
Restart=always
[Install]
WantedBy=default.target
```
```ini title="gerbil.container"
[Unit]
After=pangolin.service
Requires=pangolin.service
[Container]
ContainerName=gerbil
Image=docker.io/fosrl/gerbil:latest
Pod=services.pod
AddCapability=NET_ADMIN SYS_MODULE
Exec='--reachableAt=http://localhost:3004' '--generateAndSaveKeyTo=/var/config/key' '--remoteConfig=http://localhost:3001/api/v1/'
Volume=./config/:/var/config
[Service]
Restart=always
[Install]
WantedBy=default.target
```
```ini title="traefik.container"
[Unit]
After=pangolin.service
Requires=pangolin.service
[Container]
ContainerName=traefik
Image=docker.io/traefik:latest
Pod=services.pod
Exec='--configFile=/etc/traefik/traefik_config.yml'
Volume=./config/traefik:/etc/traefik:ro
Volume=./config/letsencrypt:/letsencrypt
Volume=./config/traefik/logs:/var/log/traefik
[Service]
Restart=always
[Install]
WantedBy=default.target
```
```ini title="services.pod"
[Unit]
Description=Pangolin Pod
Wants=network-online.target
After=network-online.target
[Pod]
PodName=services
Network=pasta
PublishPort=51820:51820/udp
PublishPort=21820:21820/udp
PublishPort=443:443
# Uncomment the line below if you enable HTTP/3 in Traefik.
# PublishPort=443:443/udp
PublishPort=80:80
[Service]
Restart=always
[Install]
WantedBy=default.target
```
<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://localhost: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
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 services.pod.
# 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 container 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://localhost:3002"
api-service:
loadBalancer:
servers:
- url: "http://localhost: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"
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).
Please note you will need to run `podman exec ...` instead of `docker exec ...`.
</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="Reload and start the services">
```bash
systemctl --user daemon-reload
systemctl --user start services-pod
```
</Step>
<Step title="Enable lingering so that services stay up after you log out">
```bash
loginctl enable-linger $USER
```
</Step>
<Step title="Watch the logs">
```bash
podman logs -f pangolin traefik gerbil
```
</Step>
<Step title="Verify the containers are healthy">
```bash
podman ps -a
```
`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
podman 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:
- `podman ps -a` shows `pangolin`, `traefik`, and `gerbil` as running.
- `podman 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 `podman 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` (or whatever alternate ports you selected on the host).
- If `pangolin` never becomes healthy, inspect `podman logs -f pangolin`.
- If tunneling does not work, inspect `podman 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 the `gerbil` block from `config/config.yml`.
That mode is covered in more detail in [Without Tunneling](/self-host/advanced/without-tunneling).
+392
View File
@@ -0,0 +1,392 @@
---
title: "Unraid Deployment"
description: "Deploy Pangolin on Unraid for local reverse proxy and tunneling"
---
## Overview
This guide explains how to use Pangolin and Traefik as a local reverse proxy without Gerbil and its tunneling features. The second (optional) part will expand on this and show how to enable tunneling by setting up Gerbil.
All containers are available in the Unraid Community Apps (CA) store. If you're not familiar with Unraid, you can find more information on their [website](https://unraid.net/).
This installation has a lot of moving parts and is a bit non-standard for Unraid because Pangolin and its components were designed to run as micro-services on a VPS in tunneling mode. However, some may want to use "Local" reverse proxying on their Unraid server or use their Unraid server as a tunnel controller with Gerbil. For either of these use cases, follow the steps outlined in this guide.
## Prerequisites
- A working Unraid server.
- A domain name with access to configure DNS and the ability to port forward on your network.
- The networking is the same as for the VPS, just on your local network, so please refer to [networking page](/self-host/dns-and-networking) for more info.
## Create a Docker Network
Before starting, create a new docker network on Unraid. This will simplify things, and allow the containers to communicate with each other via their container names. If you already have a network, there is no need to create another one.
1. Open the web terminal in Unraid.
2. Run the following command:
<Info>
You can use any name you want for the network. We will use `mynetwork` in this guide.
</Info>
```bash
docker network create mynetwork
```
For more info on this, see this [tutorial by IBRACORP](https://www.youtube.com/watch?v=7fzBDCI8O2w).
## 1. Setup Pangolin and Traefik
This first part will enable Pangolin to work in "Local" reverse proxy mode. Newt and WireGuard will **not** be able to be used after finishing this first part. However, if you want to use those features, you still need to follow this first part of the tutorial because we show how to set up Pangolin and Traefik first.
### Install and Setup Pangolin
#### 1. Create the Config Files
Pangolin uses a yaml file for configuration. If this is not present on start up, the container will throw an error and exit.
Create a `config.yml` file in the `config` folder.
See the [Configuration](/self-host/advanced/config-file) section for what to put in this file.
```
pangolin/
├─ config/
│ ├─ config.yml
```
#### 2. Install Pangolin via the CA Store
#### 3. Configure Pangolin
Set the network to the one you created earlier.
<Frame caption="Pangolin configuration settings in Unraid">
<img
src="/images/pangolin_config.png"
alt="Pangolin configuration settings in Unraid"
/>
</Frame>
**Ports:**
Due to the way Pangolin was designed to work with docker compose and a config file, the way it handles ports is a little different as compared to other popular Unraid containers. For all host ports:
The host ports, container ports, and ports in the config should match for simplicity. This is because the Pangolin config also has ports in it. If you decide to use a non-default port, you would need to edit the port in the template and the config file.
For example, to change the port for the WebUI:
- Click edit on the port
- Set the "Container Port" to the new port you want to use
- Set the "Host Port" to the new port you want to use
- Edit Pangolin's config file and set `server.next_port` to the new port you want to use
#### 4. Start the Pangolin Container
<Warning>
Pangolin will not start without a config file. If you have not created the config file or the config file is invalid, the container will throw an error and exit.
</Warning>
#### 5. Log in to the dashboard
After successful installation:
1. Complete the initial admin user setup via the dashboard at `https://<your-domain>/auth/initial-setup`
2. You can log in using the admin email and password you provided
3. Create your first "Local" site for local reverse proxying
### Install and Setup Traefik
Before starting with Traefik, shut down the Pangolin container.
#### 1. Create the Config Files
Update the appdata path with new files for Traefik. At this point there may be some extra files generated by Pangolin.
```
pangolin/
├─ config/
│ ├─ config.yml
│ ├─ letsencrypt/
│ ├─ traefik/
│ │ ├─ dynamic_config.yml
│ │ ├─ traefik_config.yml
```
**`pangolin/config/traefik/traefik_config.yml`:**
```yaml title="pangolin/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.1"
log:
level: "INFO"
format: "common"
certificatesResolvers:
letsencrypt:
acme:
httpChallenge:
entryPoint: web
email: admin@example.com # REPLACE THIS 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
```
**`pangolin/config/traefik/dynamic_config.yml`:**
The dynamic configuration file is where you define the HTTP routers and services for the Pangolin frontend and backend. Below is an example configuration for a Next.js frontend and an API backend.
The domain you enter here is what will be used to access the main Pangolin dashboard. Make sure you have the DNS set up correctly for this domain. Point it to the IP address of the server running Pangolin.
```yaml title="pangolin/config/traefik/dynamic_config.yml"
http:
middlewares:
badger:
plugin:
badger:
disableForwardAuth: true
redirect-to-https:
redirectScheme:
scheme: https
routers:
# HTTP to HTTPS redirect router
main-app-router-redirect:
rule: "Host(`pangolin.example.com`)" # REPLACE THIS WITH YOUR DOMAIN
service: next-service
entryPoints:
- web
middlewares:
- 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 THIS WITH YOUR DOMAIN
service: next-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
# API router (handles /api/v1 paths)
api-router:
rule: "Host(`pangolin.example.com`) && PathPrefix(`/api/v1`)" # REPLACE THIS WITH YOUR DOMAIN
service: api-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
# WebSocket router
ws-router:
rule: "Host(`pangolin.example.com`)" # REPLACE THIS WITH YOUR DOMAIN
service: api-service
entryPoints:
- websecure
middlewares:
- badger
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
```
#### 2. Install Traefik via the CA Store
This section will use the Traefik template from the "IBRACORP" repository. If you already have a Traefik installation running, you should manually configure your Traefik config to work with Pangolin.
<Frame caption="Traefik repository selection in Community Apps">
<img
src="/images/traefik_repo.png"
width="400"
alt="Traefik repository selection in Community Apps"
/>
</Frame>
#### 3. Configure Traefik
<Frame caption="Traefik configuration settings in Unraid">
<img
src="/images/traefik_config.png"
alt="Traefik configuration settings in Unraid"
/>
</Frame>
<Info>
Please refer to the official Traefik docs for more information on the Traefik configuration beyond this guide.
</Info>
**Match your config to the one above. You will have to remove some of the default variables in the template that are not needed. You can always add them back if you need them later.**
**Network Type:**
Set the network type to the one you created earlier.
**Post Arguments:**
Tell Traefik where the config file is located by adding the following to the "Post Arguments" field. This is not the host path, but the path inside the container.
```bash
--configFile=/etc/traefik/traefik_config.yml
```
**Config Folder:**
If you're using the Traefik config generated by Pangolin, point this to the same appdata path as Pangolin, but append `/traefik`, like this: `<appdata>/config/traefik`.
**Lets Encrypt (Host Path 2 in screenshot):**
Traefik will store the certification information here. You can make this path anywhere you want. For simplicity, we're placing it in the same config path at `<appdata>/config/letsencrypt`.
**Ports:**
You will need to port forward the https and http ports listed in the config on your network's router.
#### 4. Port Forwarding
You will need to port forward the ports you set in the Traefik config on your network's router. This is so that Traefik can receive traffic from the internet. You should forward 443 to the https port and 80 to the http port you set in the Traefik config.
## 2. Add Gerbil for Tunneling (Optional)
<Info>
If you do not want to use the tunneling feature of Pangolin and only want to use it as a local reverse proxy, you can stop here.
</Info>
Before setting up Gerbil, shut down Traefik and Pangolin.
If you plan to use tunneling features of Pangolin with Newt or WireGuard, you will need to add Gerbil to the stack. Gerbil is the tunnel controller for Pangolin and is used to manage the tunnels between the Pangolin server and the client.
Luckily, adding Gerbil is fairly easy.
The important concept to understand going forward, is we need to network Traefik through Gerbil. All Traefik traffic goes through the Gerbil container and exits.
#### 1. Install Gerbil via the CA Store
#### 2. Configure Gerbil
Set the network to the one you created earlier.
<Frame caption="Gerbil configuration settings in Unraid">
<img
src="/images/gerbil_config.png"
alt="Gerbil configuration settings in Unraid"
/>
</Frame>
**Important things to consider:**
**Internal Communication:**
Anywhere you see `http://pangolin:3001` must match. The hostname should be the name of the Pangolin container on the docker network you're using. This is because it is routed using the internal docker DNS address. The port must also match the port you have set for the internal port in Pangolin. These defaults will work unless you changed these values earlier when setting up Pangolin.
**WireGuard Port:**
<Warning>
You **must** use the default port of `51822` for WireGuard in the Gerbil container. Using any other port may cause connection issues that are difficult to debug.
Make sure this is also reflected in your Pangolin `config.yml`:
```yml
gerbil:
start_port: 51822
```
See [this GitHub issue comment](https://github.com/fosrl/pangolin/issues/227#issuecomment-2781608815) for more details.
</Warning>
The port you use for WireGuard must also match what you set the port to in the Pangolin config. By default we use a slightly different port than the standard WireGuard port to avoid conflicts with the built in WireGuard server in Unraid.
**HTTP and HTTPS Ports:**
You must open these ports because Traefik will be routed through Gerbil. These ports should match the ports you set in the Traefik config earlier. In the next step, we will set the network mode for Traefik which will close the ports on the Traefik side, and prevent conflicts. Before doing this, if you start the Traefik container at the same time as the Gerbil container with the same ports mapped to the host, you will get an error.
#### 3. Network Traefik Through Gerbil
As discussed earlier we need to network Traefik through Gerbil. This is pretty easy. We will do all of this in the Traefik container settings.
Toggle advanced settings, and add the following to the "Extra Parameters" section.
```bash
--net=container:Gerbil
```
Then, set "Network Type" to "None".
<Frame caption="Traefik networking configuration through Gerbil">
<img
src="/images/traefik_networking.png"
alt="Traefik networking configuration through Gerbil"
/>
</Frame>
#### 4. Start the stack
We recommend to start the whole stack in the following order:
1. Pangolin
2. Gerbil
3. Traefik
#### 5. Port Forwarding
You will need to port forward the WireGuard port you set in the Gerbil config on your network's router. This is so that the client can connect to the server.
#### 6. Verify Tunnels are Functional
Your logs for Gerbil should look something like this:
<Info>
You probably won't have the peer connection messages but in general, you should see the WireGuard interface being started.
</Info>
<Frame caption="Gerbil logs showing WireGuard interface startup">
<img
src="/images/gerbil_logs.png"
alt="Gerbil logs showing WireGuard interface startup"
/>
</Frame>
Log back into the Pangolin dashboard and create a new site with Newt or basic WireGuard. Copy the credentials to your client and connect. You should see the tunnel status change to "Online" after a few moments if the connection is successful. Remember to also monitor the logs on the client and server.