From a90ac38454ad6115e091d9c89cfa4777799e80a0 Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Fri, 16 Jan 2026 13:15:32 -0600 Subject: [PATCH 1/9] Add Pangolin and Netbird --- blueprints/netbird/docker-compose.yml | 72 +++++ blueprints/netbird/logo.png | Bin 0 -> 4201 bytes blueprints/netbird/template.toml | 82 +++++ blueprints/pangolin/README.md | 414 +++++++++++++++++++++++++ blueprints/pangolin/docker-compose.yml | 59 ++++ blueprints/pangolin/logo.png | Bin 0 -> 8752 bytes blueprints/pangolin/template.toml | 184 +++++++++++ meta.json | 38 +++ 8 files changed, 849 insertions(+) create mode 100644 blueprints/netbird/docker-compose.yml create mode 100644 blueprints/netbird/logo.png create mode 100644 blueprints/netbird/template.toml create mode 100644 blueprints/pangolin/README.md create mode 100644 blueprints/pangolin/docker-compose.yml create mode 100644 blueprints/pangolin/logo.png create mode 100644 blueprints/pangolin/template.toml diff --git a/blueprints/netbird/docker-compose.yml b/blueprints/netbird/docker-compose.yml new file mode 100644 index 000000000..7236b5fe1 --- /dev/null +++ b/blueprints/netbird/docker-compose.yml @@ -0,0 +1,72 @@ +version: "3.8" + +services: + netbird-management: + image: ghcr.io/netbirdio/netbird:0.63.0-rootless + restart: unless-stopped + command: netbird-mgmt + volumes: + - netbird-management:/var/lib/netbird + - netbird-ssl:/etc/letsencrypt:ro + environment: + - NETBIRD_MGMT_API_ENDPOINT=${NETBIRD_MGMT_API_ENDPOINT} + - NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN=${NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN} + - NETBIRD_MGMT_DNS_DOMAIN=${NETBIRD_MGMT_DNS_DOMAIN} + - NETBIRD_DISABLE_ANONYMOUS_METRICS=${NETBIRD_DISABLE_ANONYMOUS_METRICS} + - NETBIRD_STORE_ENGINE_POSTGRES_DSN=${NETBIRD_STORE_ENGINE_POSTGRES_DSN} + - NETBIRD_STORE_ENGINE_MYSQL_DSN=${NETBIRD_STORE_ENGINE_MYSQL_DSN} + - NETBIRD_MGMT_API_CERT_FILE=${NETBIRD_MGMT_API_CERT_FILE} + - NETBIRD_MGMT_API_CERT_KEY_FILE=${NETBIRD_MGMT_API_CERT_KEY_FILE} + + netbird-signal: + image: ghcr.io/netbirdio/netbird:0.63.0-rootless + restart: unless-stopped + command: netbird-signal + volumes: + - netbird-signal:/var/lib/netbird + - netbird-ssl:/etc/letsencrypt:ro + environment: + - NETBIRD_SIGNAL_PORT=${NETBIRD_SIGNAL_PORT} + - NETBIRD_MGMT_API_CERT_FILE=${NETBIRD_MGMT_API_CERT_FILE} + - NETBIRD_MGMT_API_CERT_KEY_FILE=${NETBIRD_MGMT_API_CERT_KEY_FILE} + depends_on: + - netbird-management + + netbird-relay: + image: ghcr.io/netbirdio/netbird:0.63.0-rootless + restart: unless-stopped + command: netbird-relay + environment: + - NB_LOG_LEVEL=${NB_LOG_LEVEL} + - NB_LISTEN_ADDRESS=${NB_LISTEN_ADDRESS} + - NB_EXPOSED_ADDRESS=${NB_EXPOSED_ADDRESS} + - NB_AUTH_SECRET=${NB_AUTH_SECRET} + + netbird-dashboard: + image: ghcr.io/netbirdio/netbird:0.63.0-rootless + restart: unless-stopped + command: netbird-dashboard + volumes: + - netbird-ssl:/etc/letsencrypt:ro + environment: + - NETBIRD_MGMT_API_ENDPOINT=${NETBIRD_MGMT_API_ENDPOINT} + - NETBIRD_MGMT_GRPC_API_ENDPOINT=${NETBIRD_MGMT_GRPC_API_ENDPOINT} + - AUTH_AUDIENCE=${AUTH_AUDIENCE} + - AUTH_CLIENT_ID=${AUTH_CLIENT_ID} + - AUTH_CLIENT_SECRET=${AUTH_CLIENT_SECRET} + - AUTH_AUTHORITY=${AUTH_AUTHORITY} + - USE_AUTH0=${USE_AUTH0} + - LETSENCRYPT_DOMAIN=${LETSENCRYPT_DOMAIN} + - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL} + depends_on: + - netbird-management + + coturn: + image: coturn/coturn:latest + restart: unless-stopped + command: -c /etc/turnserver.conf + +volumes: + netbird-management: + netbird-signal: + netbird-ssl: diff --git a/blueprints/netbird/logo.png b/blueprints/netbird/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..259df1ea08434edb8935d500893ef831aab9c382 GIT binary patch literal 4201 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 literal 0 HcmV?d00001 diff --git a/blueprints/netbird/template.toml b/blueprints/netbird/template.toml new file mode 100644 index 000000000..aea86b866 --- /dev/null +++ b/blueprints/netbird/template.toml @@ -0,0 +1,82 @@ +[variables] +main_domain = "${domain}" +mgmt_api_port = "443" +signal_port = "10000" +relay_port = "33080" +relay_auth_secret = "${password:32}" +turn_username = "${username}" +turn_password = "${password:32}" +mgmt_api_endpoint = "https://${main_domain}:${mgmt_api_port}" +mgmt_grpc_api_endpoint = "https://${main_domain}:${mgmt_api_port}" +dns_domain = "netbird.selfhosted" +letsencrypt_domain = "${main_domain}" +letsencrypt_email = "${email}" +auth_client_id = "${uuid}" +auth_client_secret = "${password:32}" +auth_authority = "https://${main_domain}" +auth_audience = "${uuid}" +mgmt_cert_file = "/etc/letsencrypt/live/${letsencrypt_domain}/fullchain.pem" +mgmt_cert_key_file = "/etc/letsencrypt/live/${letsencrypt_domain}/privkey.pem" +nb_listen_address = ":${relay_port}" +nb_exposed_address = "${main_domain}:${relay_port}" + +[config] +[[config.domains]] +serviceName = "netbird-dashboard" +port = 80 +host = "${main_domain}" + +[[config.domains]] +serviceName = "netbird-management" +port = 443 +host = "${main_domain}" + +env = [ + "NETBIRD_MGMT_API_ENDPOINT=${mgmt_api_endpoint}", + "NETBIRD_MGMT_GRPC_API_ENDPOINT=${mgmt_grpc_api_endpoint}", + "NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN=", + "NETBIRD_MGMT_DNS_DOMAIN=${dns_domain}", + "NETBIRD_DISABLE_ANONYMOUS_METRICS=false", + "NETBIRD_MGMT_API_CERT_FILE=${mgmt_cert_file}", + "NETBIRD_MGMT_API_CERT_KEY_FILE=${mgmt_cert_key_file}", + "NETBIRD_SIGNAL_PORT=${signal_port}", + "NB_LOG_LEVEL=info", + "NB_LISTEN_ADDRESS=${nb_listen_address}", + "NB_EXPOSED_ADDRESS=${nb_exposed_address}", + "NB_AUTH_SECRET=${relay_auth_secret}", + "AUTH_AUDIENCE=${auth_audience}", + "AUTH_CLIENT_ID=${auth_client_id}", + "AUTH_CLIENT_SECRET=${auth_client_secret}", + "AUTH_AUTHORITY=${auth_authority}", + "USE_AUTH0=false", + "LETSENCRYPT_DOMAIN=${letsencrypt_domain}", + "LETSENCRYPT_EMAIL=${letsencrypt_email}", + "TURN_USERNAME=${turn_username}", + "TURN_PASSWORD=${turn_password}" +] + +[[config.mounts]] +serviceName = "coturn" +filePath = "/etc/turnserver.conf" +content = """ +listening-port=3478 +tls-listening-port=5349 +listening-ip=0.0.0.0 +external-ip=${main_domain} +relay-ip=0.0.0.0 +server-name=${main_domain} +realm=${main_domain} +user=${turn_username}:${turn_password} +no-cli +no-tls +no-dtls +no-stdout-log +log-file=/var/log/turnserver.log +verbose +fingerprint +lt-cred-mech +userdb=/var/lib/turn/turndb +web-admin +web-admin-ip=0.0.0.0 +web-admin-port=8080 +""" diff --git a/blueprints/pangolin/README.md b/blueprints/pangolin/README.md new file mode 100644 index 000000000..0bac81eef --- /dev/null +++ b/blueprints/pangolin/README.md @@ -0,0 +1,414 @@ +# Pangolin Template for Dokploy + +## Overview + +Pangolin is an identity-based remote access platform that combines reverse proxy and VPN capabilities built on WireGuard. This template provides a complete deployment configuration for running Pangolin on Dokploy. + +**⚠️ Important:** This is a more complex deployment than typical Dokploy templates due to Pangolin's use of Traefik as an internal reverse proxy. Please read this README carefully before deployment. + +## Architecture + +Pangolin consists of three core services: + +1. **pangolin** - Main application (dashboard + API) + - Dashboard on port 3002 + - API on port 3001 + - Healthcheck: `/api/v1/` + +2. **gerbil** - WireGuard VPN service + - Requires `NET_ADMIN` and `SYS_MODULE` capabilities + - Exposes ports: 80, 443, 51820/udp, 21820/udp + - Stores WireGuard keys + +3. **traefik** - Internal reverse proxy + - Uses `network_mode: service:gerbil` to share gerbil's network namespace + - Handles HTTP/HTTPS routing + - Manages SSL certificates via Let's Encrypt + - Routes to pangolin services internally + +## Domain Routing Architecture + +### Standard Routing + +``` +dokploy.example.com → Dokploy (Dokploy's own routing) +pangolin.example.com → Dokploy → Traefik → Pangolin Dashboard +myapp.example.com → Dokploy → Your App (standard Dokploy routing) +mylocalapp.example.com → Dokploy → Pangolin → Traefik → Local Resource +``` + +### How It Works + +1. **Dokploy handles initial domain routing** - When you assign a domain to the Pangolin deployment, Dokploy's Traefik routes traffic to the `gerbil` service (port 80) +2. **Pangolin's Traefik handles internal routing** - Traefik inside the deployment receives the request and routes it to the appropriate Pangolin service (dashboard or API) +3. **Pangolin can proxy to other resources** - Once configured, Pangolin can proxy to other Dokploy apps or external resources + +## Deployment + +### Basic Deployment + +1. Deploy the template via Dokploy +2. Assign a domain (e.g., `pangolin.example.com`) +3. Wait for services to start (pangolin healthcheck must pass before gerbil and traefik start) +4. Access the dashboard at your assigned domain +5. Complete the initial setup in the Pangolin dashboard + +### Configuration + +The template automatically configures: + +- **Pangolin config** (`/app/config/config.yml`) - Main application configuration +- **Traefik static config** (`/etc/traefik/traefik_config.yml`) - Traefik entrypoints and certificate resolver +- **Traefik dynamic config** (`/etc/traefik/dynamic_config.yml`) - Routing rules for dashboard and API +- **SSL certificates** - Managed via Let's Encrypt + +### Environment Variables + +The template uses Dokploy's variable helpers: + +- `main_domain` - Your assigned domain (auto-generated) +- `pangolin_secret` - Secret key for Pangolin (auto-generated, 64 chars) +- `letsencrypt_email` - Email for Let's Encrypt certificates (auto-generated) + +## Advanced: Catch-All Routing Configuration + +### Overview + +You can configure Dokploy's Traefik to forward all unknown/unmatched domains to Pangolin. This allows Pangolin to act as a catch-all reverse proxy for domains not explicitly configured in Dokploy. + +**Use Case:** When you want Pangolin to handle routing for domains that aren't explicitly set up in Dokploy, such as: +- Dynamic subdomains +- Local development domains +- Temporary domains for testing + +### How Catch-All Routing Works + +``` +Request Flow: +1. Client → unknown-domain.example.com +2. Dokploy Traefik receives request +3. No explicit router matches the domain +4. Catch-all router (lowest priority) matches and forwards to Pangolin +5. Pangolin's Traefik receives the request and handles routing +6. Pangolin routes to configured resource (local, WireGuard, or other) +``` + +### Configuration Steps + +#### Step 1: Deploy Pangolin Template + +1. Deploy the Pangolin template via Dokploy +2. Note the service name (typically `{project-name}-gerbil` or similar) + - You can find this by checking your deployment in Dokploy + - Or by running: `docker ps | grep gerbil` + +#### Step 2: Verify Pangolin Configuration + +The template already configures Pangolin with `trust_proxy: 1` in the config file, which allows it to properly handle forwarded requests from Dokploy's Traefik. This is required for catch-all routing. + +#### Step 3: Create Catch-All Router in Dokploy's Traefik + +**Option A: Via Dokploy API (if available)** + +Use Dokploy's API endpoints to create the catch-all configuration: +- Endpoint: `/api/settings/traefik/file` +- Method: `POST` or `PUT` +- Create file: `pangolin-catchall.yml` + +**Option B: Manual File Creation (Recommended)** + +1. SSH into your Dokploy server +2. Navigate to `/etc/dokploy/traefik/dynamic/` (production) or `.docker/traefik/dynamic/` (development) +3. Create a new file: `pangolin-catchall.yml` +4. Add the following configuration: + +```yaml +http: + routers: + pangolin-catchall: + rule: "HostRegexp(`^.+$`)" + priority: 1 # Lowest priority - only matches when nothing else does + service: pangolin-forward + entryPoints: + - web + - websecure + middlewares: + - pangolin-headers + tls: + certResolver: letsencrypt + + middlewares: + pangolin-headers: + headers: + customRequestHeaders: + X-Forwarded-Proto: "https" + X-Forwarded-Host: "{host}" + + services: + pangolin-forward: + loadBalancer: + servers: + - url: "http://pangolin-gerbil:80" # Replace with your actual service name + passHostHeader: true +``` + +**Important Notes:** + +- Replace `pangolin-gerbil` with your actual service name + - Format is typically: `{project-name}-{service-name}` + - Example: If your project is named "pangolin", service name would be `pangolin-gerbil` + - Example: If your project is named "my-pangolin", service name would be `my-pangolin-gerbil` +- Use `priority: 1` to ensure this only matches when no other router matches +- `passHostHeader: true` preserves the original Host header for Pangolin +- Traefik will automatically reload when the file is created/modified + +#### Step 4: Find Your Service Name + +To find the exact service name: + +```bash +# Method 1: Check running containers +docker ps | grep gerbil + +# Method 2: Check Dokploy project name +# In Dokploy UI, check your project name +# Service name will be: {project-name}-gerbil + +# Method 3: Inspect Docker network +docker network inspect dokploy-network | grep -A 5 gerbil +``` + +#### Step 5: Verify Configuration + +1. **Check Traefik logs** to verify the configuration loaded: + ```bash + docker logs traefik | grep "Configuration loaded" + ``` + +2. **Test with an unknown domain**: + ```bash + curl -H "Host: test.example.com" http://your-server + ``` + +3. **Check Pangolin dashboard** - Access your Pangolin dashboard and verify it receives forwarded requests + +4. **Monitor Traefik logs** for routing: + ```bash + docker logs traefik -f + ``` + +#### Step 6: Configure Pangolin for Dynamic Routing + +1. Access Pangolin dashboard at your assigned domain +2. Configure sites and resources as needed +3. Set up domain routing in Pangolin for the forwarded domains +4. Pangolin's Traefik will handle SSL certificates via Let's Encrypt + +### Troubleshooting Catch-All Routing + +#### Issue: 404 errors on unknown domains + +**Possible causes:** +- Catch-all router priority is not 1 (lowest) +- Service name doesn't match actual deployment +- Traefik hasn't reloaded the configuration + +**Solutions:** +1. Verify router priority is `1` (lowest) +2. Check service name matches actual deployment: `docker ps | grep gerbil` +3. Force Traefik reload: `docker exec traefik killall -HUP traefik` +4. Check Traefik logs: `docker logs traefik | grep "pangolin-catchall"` + +#### Issue: SSL certificate errors + +**Possible causes:** +- Let's Encrypt resolver not configured in catch-all router +- Domain DNS doesn't point to server +- Pangolin's Traefik can't access Let's Encrypt + +**Solutions:** +1. Ensure `certResolver: letsencrypt` is in the catch-all router +2. Verify domain DNS points to your server +3. Check Pangolin's Traefik logs for certificate issues +4. Verify Let's Encrypt email is configured + +#### Issue: Headers not forwarded correctly + +**Possible causes:** +- `trust_proxy: 1` not set in Pangolin config +- Middleware not preserving headers +- Headers being stripped by Dokploy's Traefik + +**Solutions:** +1. Verify `trust_proxy: 1` in Pangolin config (already set in template) +2. Check middleware configuration preserves headers +3. Review Traefik logs for header forwarding +4. Test with `curl -v` to see actual headers + +#### Issue: Service not found + +**Possible causes:** +- Service name incorrect +- Service not running +- Network connectivity issues + +**Solutions:** +1. Verify service name: `docker ps | grep gerbil` +2. Check service is running: `docker ps | grep pangolin` +3. Test network connectivity: `docker network inspect dokploy-network` +4. Verify service is on the same network as Traefik + +### Alternative: Path-Based Catch-All + +If you prefer path-based routing instead of domain-based: + +```yaml +http: + routers: + pangolin-catchall: + rule: "PathPrefix(`/`)" + priority: 1 + service: pangolin-forward +``` + +This forwards all unmatched paths to Pangolin, regardless of domain. + +### Security Considerations + +1. **Rate Limiting**: Consider adding rate limiting middleware to catch-all router +2. **Access Control**: Pangolin handles authentication, but consider firewall rules +3. **SSL/TLS**: Ensure proper certificate handling for forwarded domains +4. **Logging**: Monitor catch-all router for abuse or misconfiguration +5. **Resource Limits**: Set appropriate resource limits for Pangolin services + +## Limitations and Considerations + +### Network Mode Dependency + +- Traefik uses `network_mode: service:gerbil` which shares gerbil's network namespace +- This is a Docker feature that should work with Dokploy, but may have limitations +- If issues occur, you may need to adjust the network configuration + +### Capabilities Required + +- Gerbil requires `NET_ADMIN` and `SYS_MODULE` capabilities for WireGuard +- Verify Dokploy supports `cap_add` in docker-compose +- May require special Dokploy configuration or permissions + +### Port Handling + +- Traefik listens on ports 80/443 inside gerbil's network namespace +- Dokploy routes to the service externally, so internal ports are fine +- No port conflicts should occur as Dokploy handles external routing + +### SSL Certificate Management + +- SSL certificates are managed by Traefik/Let's Encrypt inside the deployment +- Not managed by Dokploy's certificate system +- Certificates stored in `pangolin-letsencrypt` volume + +### Resource Usage + +- Pangolin runs three services (pangolin, gerbil, traefik) +- Higher resource usage than typical single-service templates +- Monitor resource usage and adjust limits as needed + +## Configuration Files + +### Pangolin Config (`/app/config/config.yml`) + +Main configuration file for Pangolin. Key settings: + +- `app.dashboard_url` - Dashboard URL (auto-configured from domain) +- `server.secret` - Server secret (auto-generated) +- `server.trust_proxy` - Set to `1` for catch-all routing support +- `gerbil.base_endpoint` - Gerbil endpoint (auto-configured from domain) +- `traefik.cert_resolver` - Let's Encrypt resolver + +### Traefik Static Config (`/etc/traefik/traefik_config.yml`) + +Traefik entrypoints and certificate resolver configuration: + +- Entry points: `:80` (web) and `:443` (websecure) +- Let's Encrypt certificate resolver +- File provider for dynamic configuration +- Logging configuration + +### Traefik Dynamic Config (`/etc/traefik/dynamic_config.yml`) + +Traefik routing rules: + +- HTTP to HTTPS redirect middleware +- Dashboard router (Next.js on port 3002) +- API router (`/api/v1` paths on port 3001) +- WebSocket router +- Services pointing to pangolin containers + +## Volumes + +The template creates the following volumes: + +- `pangolin-config` - Pangolin configuration and database +- `pangolin-traefik` - Traefik configuration files +- `pangolin-letsencrypt` - SSL certificates (Let's Encrypt) +- `pangolin-traefik-logs` - Traefik logs + +## Health Checks + +- **pangolin**: Healthcheck at `/api/v1/` endpoint + - Interval: 10s + - Timeout: 10s + - Retries: 15 + - Start period: 30s + +- **gerbil** and **traefik**: Depend on pangolin healthcheck passing + +## Support and Resources + +- **GitHub**: https://github.com/fosrl/pangolin +- **Website**: https://pangolin.net +- **Documentation**: https://docs.pangolin.net +- **Dokploy Templates**: https://github.com/dokploy/templates + +## Troubleshooting + +### Services Not Starting + +1. Check logs: `docker logs {service-name}` +2. Verify healthcheck: `docker ps` (check health status) +3. Check resource limits +4. Verify network connectivity + +### Dashboard Not Accessible + +1. Verify domain is correctly assigned in Dokploy +2. Check Traefik logs for routing issues +3. Verify SSL certificate generation +4. Check firewall rules + +### VPN Not Working + +1. Verify gerbil service is running +2. Check WireGuard key generation +3. Verify network capabilities (`NET_ADMIN`, `SYS_MODULE`) +4. Check firewall rules for UDP ports (51820, 21820) + +### API Errors + +1. Check pangolin service logs +2. Verify API endpoint is accessible +3. Check Traefik routing rules +4. Verify CORS configuration if needed + +## Contributing + +If you encounter issues or have suggestions for improving this template, please: + +1. Check existing issues in the repository +2. Create a new issue with detailed information +3. Include logs and configuration details +4. Test in a clean environment if possible + +## License + +This template follows the same license as the Dokploy Templates repository. diff --git a/blueprints/pangolin/docker-compose.yml b/blueprints/pangolin/docker-compose.yml new file mode 100644 index 000000000..aad536f8a --- /dev/null +++ b/blueprints/pangolin/docker-compose.yml @@ -0,0 +1,59 @@ +version: "3.8" + +services: + pangolin: + image: fosrl/pangolin:latest + restart: unless-stopped + volumes: + - pangolin-config:/app/config + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"] + interval: 10s + timeout: 10s + retries: 15 + start_period: 30s + expose: + - "3001" + - "3002" + + gerbil: + image: fosrl/gerbil:latest + 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: + - pangolin-config:/var/config + cap_add: + - NET_ADMIN + - SYS_MODULE + expose: + - "80" + - "443" + - "51820/udp" + - "21820/udp" + - "3004" + + traefik: + image: traefik:v3.6 + restart: unless-stopped + network_mode: service:gerbil + depends_on: + pangolin: + condition: service_healthy + command: + - --configFile=/etc/traefik/traefik_config.yml + volumes: + - pangolin-traefik:/etc/traefik:ro + - pangolin-letsencrypt:/letsencrypt + - pangolin-traefik-logs:/var/log/traefik + +volumes: + pangolin-config: + pangolin-traefik: + pangolin-letsencrypt: + pangolin-traefik-logs: diff --git a/blueprints/pangolin/logo.png b/blueprints/pangolin/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..71f27b294e563bc4da8fab549a1f4f67bec7b5fb GIT binary patch literal 8752 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 S)8I`I=$W#HQniBR%l`pP3c)A< literal 0 HcmV?d00001 diff --git a/blueprints/pangolin/template.toml b/blueprints/pangolin/template.toml new file mode 100644 index 000000000..0dfe22723 --- /dev/null +++ b/blueprints/pangolin/template.toml @@ -0,0 +1,184 @@ +[variables] +main_domain = "${domain}" +pangolin_secret = "${base64:64}" +letsencrypt_email = "${email}" +gerbil_endpoint = "${main_domain}" + +[[config.domains]] +serviceName = "gerbil" +port = 80 +host = "${main_domain}" + +[config.env] +SERVER_SECRET = "${pangolin_secret}" + +[[config.mounts]] +filePath = "/app/config/config.yml" +content = """ +app: + dashboard_url: "https://${main_domain}" + log_level: "info" + +domains: + domain1: + base_domain: "${main_domain}" + cert_resolver: "letsencrypt" + prefer_wildcard_cert: false + +server: + integration_port: 3003 + external_port: 3000 + internal_port: 3001 + next_port: 3002 + internal_hostname: "pangolin" + session_cookie_name: "p_session_token" + resource_access_token_param: "p_token" + resource_access_token_headers: + id: "P-Access-Token-Id" + token: "P-Access-Token" + dashboard_session_length_hours: 720 + resource_session_length_hours: 720 + trust_proxy: 1 + secret: "${pangolin_secret}" + +gerbil: + base_endpoint: "${gerbil_endpoint}" + use_subnet: false + subnet_group: "100.89.137.0/20" + block_size: 24 + site_block_size: 30 + +orgs: + block_size: 24 + subnet_group: "100.90.128.0/20" + utility_subnet_group: "100.96.128.0/20" + +traefik: + http_entrypoint: "web" + https_entrypoint: "websecure" + cert_resolver: "letsencrypt" + prefer_wildcard_cert: false + file_mode: true + site_types: ["newt", "wireguard", "local"] + allow_raw_resources: true + +flags: + require_email_verification: false + disable_signup_without_invite: false + disable_user_create_org: false + allow_raw_resources: false + enable_integration_api: false + disable_local_sites: false + disable_basic_wireguard_sites: false +""" + +[[config.mounts]] +filePath = "/etc/traefik/traefik_config.yml" +content = """ +api: + insecure: true + dashboard: true + +providers: + file: + filename: "/etc/traefik/dynamic_config.yml" + watch: true + +log: + level: "INFO" + format: "common" + maxSize: 100 + maxBackups: 3 + maxAge: 3 + compress: true + +certificatesResolvers: + letsencrypt: + acme: + httpChallenge: + entryPoint: web + email: "${letsencrypt_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 + encodedCharacters: + allowEncodedSlash: true + allowEncodedQuestionMark: true + +serversTransport: + insecureSkipVerify: true + +ping: + entryPoint: "web" +""" + +[[config.mounts]] +filePath = "/etc/traefik/dynamic_config.yml" +content = """ +http: + middlewares: + redirect-to-https: + redirectScheme: + scheme: https + + routers: + # HTTP to HTTPS redirect router + main-app-router-redirect: + rule: "Host(`${main_domain}`)" + service: next-service + entryPoints: + - web + middlewares: + - redirect-to-https + + # Next.js router (handles everything except API and WebSocket paths) + next-router: + rule: "Host(`${main_domain}`) && !PathPrefix(`/api/v1`)" + service: next-service + priority: 10 + entryPoints: + - websecure + tls: + certResolver: letsencrypt + + # API router (handles /api/v1 paths) + api-router: + rule: "Host(`${main_domain}`) && PathPrefix(`/api/v1`)" + service: api-service + priority: 100 + entryPoints: + - websecure + tls: + certResolver: letsencrypt + + # WebSocket router + ws-router: + rule: "Host(`${main_domain}`)" + service: api-service + entryPoints: + - websecure + tls: + certResolver: letsencrypt + + services: + next-service: + loadBalancer: + servers: + - url: "http://pangolin:3002" + + api-service: + loadBalancer: + servers: + - url: "http://pangolin:3001" +""" diff --git a/meta.json b/meta.json index ffff0e0ba..ebf99be9e 100644 --- a/meta.json +++ b/meta.json @@ -4111,6 +4111,25 @@ "remote" ] }, + { + "id": "netbird", + "name": "Netbird", + "version": "0.63.0", + "description": "Netbird is an open-source platform that combines a peer-to-peer private network with centralized access control, using WireGuard to create secure overlay networks without complex firewall configurations.", + "logo": "logo.png", + "links": { + "github": "https://github.com/netbirdio/netbird", + "website": "https://netbird.io/", + "docs": "https://docs.netbird.io/" + }, + "tags": [ + "networking", + "vpn", + "wireguard", + "self-hosted", + "security" + ] + }, { "id": "netdata", "name": "Netdata", @@ -4613,6 +4632,25 @@ "open-source" ] }, + { + "id": "pangolin", + "name": "Pangolin", + "version": "latest", + "description": "Identity-based remote access platform with reverse proxy and VPN capabilities built on WireGuard. ⚠️ This is a complex deployment - please check the README.md in the blueprint directory for detailed setup instructions, including catch-all routing configuration.", + "logo": "logo.png", + "links": { + "github": "https://github.com/fosrl/pangolin", + "website": "https://pangolin.net", + "docs": "https://docs.pangolin.net" + }, + "tags": [ + "vpn", + "remote-access", + "reverse-proxy", + "wireguard", + "networking" + ] + }, { "id": "parseable", "name": "Parseable", From a6c3ce3d11dec1a02ca562dd1bf609864b478b93 Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Fri, 16 Jan 2026 15:28:34 -0600 Subject: [PATCH 2/9] Update Pangolin --- blueprints/pangolin/README.md | 474 ++++++++++++++++++++++++- blueprints/pangolin/docker-compose.yml | 6 +- blueprints/pangolin/template.toml | 2 +- 3 files changed, 473 insertions(+), 9 deletions(-) diff --git a/blueprints/pangolin/README.md b/blueprints/pangolin/README.md index 0bac81eef..5ccfc7a51 100644 --- a/blueprints/pangolin/README.md +++ b/blueprints/pangolin/README.md @@ -21,10 +21,10 @@ Pangolin consists of three core services: - Stores WireGuard keys 3. **traefik** - Internal reverse proxy - - Uses `network_mode: service:gerbil` to share gerbil's network namespace - - Handles HTTP/HTTPS routing + - Handles HTTP/HTTPS routing on ports 80/443 - Manages SSL certificates via Let's Encrypt - Routes to pangolin services internally + - Communicates with gerbil and pangolin via Docker network ## Domain Routing Architecture @@ -202,7 +202,77 @@ docker network inspect dokploy-network | grep -A 5 gerbil 1. Access Pangolin dashboard at your assigned domain 2. Configure sites and resources as needed 3. Set up domain routing in Pangolin for the forwarded domains -4. Pangolin's Traefik will handle SSL certificates via Let's Encrypt +4. **SSL Handling**: See "SSL/TLS Certificate Handling" section below for important details + +### SSL/TLS Certificate Handling in Proxy Chains + +**Important**: SSL certificate handling depends on how Dokploy's Traefik is configured. + +#### Scenario: `examplessltest.example.com` → Dokploy → Pangolin → Service + +**Current Setup (HTTP Router - Recommended):** + +When using an HTTP router in Dokploy's catch-all configuration (as shown above): + +1. **Dokploy's Traefik terminates SSL** - It decrypts HTTPS requests for `examplessltest.example.com` +2. **Forwards HTTP to Pangolin** - Pangolin's Traefik receives plain HTTP (not HTTPS) +3. **Pangolin forwards to service** - The final service receives HTTP + +**SSL Status:** +- ✅ **SSL works at Dokploy level** - Client to Dokploy is encrypted +- ✅ **Dokploy generates certificate** - Let's Encrypt certificate for `examplessltest.example.com` is managed by Dokploy +- ⚠️ **Pangolin sees HTTP** - Pangolin's Traefik receives HTTP, not HTTPS +- ✅ **Service receives HTTP** - Final service receives HTTP (can be HTTPS if service requires it) + +**This setup works and is the simplest approach.** SSL is handled by Dokploy, and Pangolin acts as an HTTP proxy. + +#### Alternative: TCP Passthrough (Advanced) + +If you need Pangolin's Traefik to handle SSL termination instead: + +1. **Use TCP router in Dokploy** with `passthrough: true` +2. **SSL passes through** to Pangolin's Traefik +3. **Pangolin terminates SSL** and can generate its own certificates + +**Configuration for TCP Passthrough:** + +```yaml +tcp: + routers: + pangolin-catchall-tcp: + rule: "HostSNI(`*`)" # Matches all domains + service: pangolin-forward-tcp + entryPoints: + - websecure + tls: + passthrough: true # Pass SSL through to Pangolin + + services: + pangolin-forward-tcp: + loadBalancer: + servers: + - address: "pangolin-gerbil:443" # Forward to HTTPS port +``` + +**Important Notes for TCP Passthrough:** +- More complex configuration +- Pangolin's Traefik must handle SSL termination +- Pangolin can generate certificates via Let's Encrypt +- Requires Pangolin's Traefik to be accessible on port 443 +- Let's Encrypt HTTP-01 challenge must be able to reach Pangolin on port 80 + +#### Recommendation + +**For most use cases, use the HTTP router approach (current setup):** +- Simpler configuration +- Dokploy handles SSL certificates +- Works reliably with catch-all routing +- Pangolin receives HTTP and forwards to services + +**Use TCP passthrough only if:** +- You need Pangolin to manage SSL certificates +- You want end-to-end SSL visibility in Pangolin +- You have specific requirements for certificate management ### Troubleshooting Catch-All Routing @@ -283,11 +353,12 @@ This forwards all unmatched paths to Pangolin, regardless of domain. ## Limitations and Considerations -### Network Mode Dependency +### Network Configuration -- Traefik uses `network_mode: service:gerbil` which shares gerbil's network namespace -- This is a Docker feature that should work with Dokploy, but may have limitations -- If issues occur, you may need to adjust the network configuration +- All services communicate via Docker's default bridge network +- Traefik listens on ports 80/443 internally +- Dokploy routes external traffic to traefik service +- Services can communicate using service names (pangolin, gerbil, traefik) ### Capabilities Required @@ -370,6 +441,375 @@ The template creates the following volumes: - **Documentation**: https://docs.pangolin.net - **Dokploy Templates**: https://github.com/dokploy/templates +## Testing Your Pull Request + +Before your PR is merged, you should test the template using the PR preview system. Here's how: + +### Step 1: Create and Push Your PR + +1. **Create your PR** with the Pangolin template changes +2. **Wait for CI/CD** - GitHub Actions will automatically: + - Validate `meta.json` structure + - Build a preview deployment + - Deploy to Cloudflare Pages +3. **Find the preview URL** - Check the PR description or GitHub Actions logs for the preview deployment link + +### Step 2: Access the Preview + +1. **Open the preview URL** from your PR (usually in PR description or GitHub Actions) +2. **Search for "pangolin"** in the template search +3. **Click on the Pangolin template card** to view details + +### Step 3: Import Template to Dokploy + +1. **Copy the Base64 configuration**: + - In the preview, scroll down on the Pangolin template card + - Click the "Copy" button next to "Base64 Configuration" + - This copies the complete template configuration + +2. **Import into your Dokploy instance**: + - Go to your Dokploy instance + - Create a new **Compose Service** + - Go to **Advanced** section + - Scroll down to **Import** section + - Paste the Base64 value + - Click **Import** + +3. **Verify import**: + - You should see a modal with all details: + - Compose File (docker-compose.yml) + - Environment Variables + - Mounts (config files) + - Domains configuration + - Review the configuration to ensure everything is correct + +### Step 4: Deploy and Test + +1. **Deploy the service**: + - Click **Deploy** button + - Wait for deployment to complete + - Monitor service status in Dokploy + +2. **Verify services start**: + - Check that all three services (pangolin, gerbil, traefik) are running + - Verify healthcheck status + - Check service logs for any errors + +3. **Test domain access**: + - Assign a domain to the service in Dokploy + - Wait for SSL certificate generation + - Access the domain via HTTPS + - Verify dashboard loads + +4. **Test functionality**: + - Complete initial setup in Pangolin dashboard + - Test API endpoints + - Verify SSL certificate is valid + - Test basic functionality + +### Step 5: Test Edge Cases + +1. **Restart services**: + - Restart the deployment + - Verify services come back up correctly + - Check data persistence (volumes) + +2. **Test environment variables**: + - Verify all variables are set correctly + - Check that secrets are generated properly + +3. **Test volume persistence**: + - Verify config files persist + - Check SSL certificates persist + - Verify database/data persistence + +### Step 6: Local Testing (Optional) + +You can also test locally before pushing: + +```bash +# Install dependencies +cd app +pnpm install + +# Run development server +pnpm run dev + +# Visit http://localhost:5173/ +# Search for your template and test the preview locally +``` + +### What to Check + +- ✅ Template appears in preview search +- ✅ Base64 import works correctly +- ✅ All services deploy successfully +- ✅ Healthchecks pass +- ✅ Domain is accessible +- ✅ SSL certificate generates +- ✅ Dashboard loads +- ✅ No errors in logs +- ✅ Configuration files are mounted correctly +- ✅ Environment variables are set + +### Common Issues + +**Preview not building:** +- Check GitHub Actions logs +- Verify `meta.json` is valid (run `node dedupe-and-sort-meta.js`) +- Check for syntax errors in YAML/TOML files + +**Import fails:** +- Verify Base64 is copied completely +- Check for special characters in config +- Ensure template structure is correct + +**Deployment fails:** +- Check service logs +- Verify Docker Compose syntax +- Check for missing dependencies +- Verify network capabilities (NET_ADMIN, SYS_MODULE) + +**Services not starting:** +- Check healthcheck configuration +- Verify image tags are correct +- Check resource limits +- Review service dependencies + +## Testing Your Deployment + +### Step 1: Verify Services Are Running + +**Check all services are up:** +```bash +# In Dokploy UI: Check service status +# Or via command line: +docker ps | grep pangolin +docker ps | grep gerbil +docker ps | grep traefik +``` + +**Expected output:** All three services should show as "Up" with healthy status. + +**Check service health:** +```bash +# Check pangolin healthcheck +docker exec curl -f http://localhost:3001/api/v1/ + +# Check service logs +docker logs +docker logs +docker logs +``` + +### Step 2: Verify Domain Accessibility + +**Test HTTP access:** +```bash +# Should redirect to HTTPS +curl -I http://your-pangolin-domain.com + +# Expected: 301 or 302 redirect to HTTPS +``` + +**Test HTTPS access:** +```bash +# Test HTTPS connection +curl -I https://your-pangolin-domain.com + +# Expected: 200 OK or dashboard content +``` + +**Test with browser:** +1. Navigate to `https://your-pangolin-domain.com` +2. Check browser shows valid SSL certificate +3. Verify dashboard loads correctly + +### Step 3: Verify SSL Certificate + +**Check certificate details:** +```bash +# View certificate information +openssl s_client -connect your-pangolin-domain.com:443 -servername your-pangolin-domain.com < /dev/null 2>/dev/null | openssl x509 -noout -text + +# Or use online tools: +# - https://www.ssllabs.com/ssltest/ +# - https://crt.sh/ +``` + +**Verify Let's Encrypt certificate:** +```bash +# Check certificate in Traefik volume +docker exec ls -la /letsencrypt/ + +# Should see acme.json file +``` + +**Expected:** +- Certificate issued by "Let's Encrypt" +- Valid expiration date (90 days from issue) +- Certificate matches your domain + +### Step 4: Test Pangolin Dashboard + +**Access dashboard:** +1. Navigate to `https://your-pangolin-domain.com` +2. Complete initial setup (if first time) +3. Verify you can log in + +**Test API endpoints:** +```bash +# Test API health endpoint +curl https://your-pangolin-domain.com/api/v1/ + +# Test with authentication (after login) +curl -H "Authorization: Bearer " https://your-pangolin-domain.com/api/v1/ +``` + +**Expected:** +- Dashboard loads without errors +- API responds with JSON +- No console errors in browser DevTools + +### Step 5: Test Catch-All Routing (If Configured) + +**Test unknown domain:** +```bash +# Test with a domain not configured in Dokploy +curl -H "Host: test-unknown.example.com" http://your-server-ip + +# Or test via DNS (if configured) +curl https://test-unknown.example.com +``` + +**Verify routing:** +1. Check Dokploy Traefik logs for catch-all match +2. Check Pangolin receives the request +3. Verify Pangolin can route to configured resources + +**Expected:** +- Request reaches Pangolin +- Pangolin can handle routing +- No 404 errors from Dokploy + +### Step 6: Test VPN Functionality (WireGuard) + +**Verify gerbil service:** +```bash +# Check gerbil is running +docker ps | grep gerbil + +# Check WireGuard keys generated +docker exec ls -la /var/config/ + +# Should see key file +``` + +**Test WireGuard connection:** +1. In Pangolin dashboard, create a WireGuard site +2. Download WireGuard configuration +3. Import into WireGuard client +4. Connect and verify connectivity + +**Expected:** +- Gerbil service running +- Keys generated successfully +- Can connect via WireGuard client + +### Step 7: Test Resource Proxying + +**Create a test resource in Pangolin:** +1. Access Pangolin dashboard +2. Create a new resource (local, WireGuard, or other) +3. Configure domain routing +4. Test access to the resource + +**Test proxying:** +```bash +# Test access to proxied resource +curl https://your-resource-domain.com + +# Should reach the target service +``` + +**Expected:** +- Resource accessible via configured domain +- SSL certificate valid (if applicable) +- Content loads correctly + +### Step 8: Monitor Logs + +**Watch service logs:** +```bash +# Follow all service logs +docker logs -f +docker logs -f +docker logs -f + +# Or via Dokploy UI: View logs for each service +``` + +**Check for errors:** +- Look for error messages +- Check for connection failures +- Verify certificate generation logs +- Monitor healthcheck status + +### Step 9: Performance Testing + +**Test response times:** +```bash +# Measure response time +time curl -o /dev/null -s -w "%{time_total}\n" https://your-pangolin-domain.com + +# Test API response time +time curl -o /dev/null -s https://your-pangolin-domain.com/api/v1/ +``` + +**Expected:** +- Dashboard loads in < 2 seconds +- API responds in < 500ms +- No timeout errors + +### Step 10: Security Testing + +**Verify SSL/TLS:** +```bash +# Test SSL configuration +nmap --script ssl-enum-ciphers -p 443 your-pangolin-domain.com + +# Check for vulnerabilities +testssl.sh your-pangolin-domain.com +``` + +**Verify headers:** +```bash +# Check security headers +curl -I https://your-pangolin-domain.com + +# Should see appropriate headers +``` + +**Expected:** +- Strong cipher suites +- No known vulnerabilities +- Appropriate security headers + +### Quick Test Checklist + +- [ ] All three services (pangolin, gerbil, traefik) are running +- [ ] Services show healthy status +- [ ] Domain is accessible via HTTPS +- [ ] SSL certificate is valid and from Let's Encrypt +- [ ] Dashboard loads without errors +- [ ] Can log in to dashboard +- [ ] API endpoints respond correctly +- [ ] Catch-all routing works (if configured) +- [ ] WireGuard VPN can connect (if configured) +- [ ] Resources can be proxied +- [ ] No errors in service logs +- [ ] Performance is acceptable + ## Troubleshooting ### Services Not Starting @@ -379,6 +819,11 @@ The template creates the following volumes: 3. Check resource limits 4. Verify network connectivity +**Common issues:** +- Healthcheck failing: Check if pangolin API is accessible +- Port conflicts: Verify no other services use same ports +- Resource limits: Check if containers have enough memory/CPU + ### Dashboard Not Accessible 1. Verify domain is correctly assigned in Dokploy @@ -386,6 +831,11 @@ The template creates the following volumes: 3. Verify SSL certificate generation 4. Check firewall rules +**Common issues:** +- DNS not pointing to server: Verify DNS A record +- Certificate not generated: Check Let's Encrypt logs +- Routing misconfiguration: Verify Traefik dynamic config + ### VPN Not Working 1. Verify gerbil service is running @@ -393,6 +843,11 @@ The template creates the following volumes: 3. Verify network capabilities (`NET_ADMIN`, `SYS_MODULE`) 4. Check firewall rules for UDP ports (51820, 21820) +**Common issues:** +- Capabilities not available: Check Docker/container permissions +- Firewall blocking UDP: Open ports 51820 and 21820 +- Key generation failed: Check gerbil logs + ### API Errors 1. Check pangolin service logs @@ -400,6 +855,11 @@ The template creates the following volumes: 3. Check Traefik routing rules 4. Verify CORS configuration if needed +**Common issues:** +- API not responding: Check pangolin healthcheck +- Routing misconfiguration: Verify Traefik dynamic config +- CORS errors: Check browser console for details + ## Contributing If you encounter issues or have suggestions for improving this template, please: diff --git a/blueprints/pangolin/docker-compose.yml b/blueprints/pangolin/docker-compose.yml index aad536f8a..d60ca5ed2 100644 --- a/blueprints/pangolin/docker-compose.yml +++ b/blueprints/pangolin/docker-compose.yml @@ -41,16 +41,20 @@ services: traefik: image: traefik:v3.6 restart: unless-stopped - network_mode: service:gerbil depends_on: pangolin: condition: service_healthy + gerbil: + condition: service_started command: - --configFile=/etc/traefik/traefik_config.yml volumes: - pangolin-traefik:/etc/traefik:ro - pangolin-letsencrypt:/letsencrypt - pangolin-traefik-logs:/var/log/traefik + expose: + - "80" + - "443" volumes: pangolin-config: diff --git a/blueprints/pangolin/template.toml b/blueprints/pangolin/template.toml index 0dfe22723..ec1f8a525 100644 --- a/blueprints/pangolin/template.toml +++ b/blueprints/pangolin/template.toml @@ -5,7 +5,7 @@ letsencrypt_email = "${email}" gerbil_endpoint = "${main_domain}" [[config.domains]] -serviceName = "gerbil" +serviceName = "traefik" port = 80 host = "${main_domain}" From bd8c4b9e1d065720d99ae5e06fa6340142d52936 Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Sun, 18 Jan 2026 17:11:09 -0600 Subject: [PATCH 3/9] Another setup to try --- blueprints/netbird/docker-compose.yml | 4 +++- blueprints/netbird/template.toml | 10 +++++++--- blueprints/pangolin/docker-compose.yml | 12 ++++++------ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/blueprints/netbird/docker-compose.yml b/blueprints/netbird/docker-compose.yml index 7236b5fe1..b15517c20 100644 --- a/blueprints/netbird/docker-compose.yml +++ b/blueprints/netbird/docker-compose.yml @@ -4,7 +4,7 @@ services: netbird-management: image: ghcr.io/netbirdio/netbird:0.63.0-rootless restart: unless-stopped - command: netbird-mgmt + command: netbird-mgmt management volumes: - netbird-management:/var/lib/netbird - netbird-ssl:/etc/letsencrypt:ro @@ -55,6 +55,8 @@ services: - AUTH_CLIENT_ID=${AUTH_CLIENT_ID} - AUTH_CLIENT_SECRET=${AUTH_CLIENT_SECRET} - AUTH_AUTHORITY=${AUTH_AUTHORITY} + - AUTH_REDIRECT_URI=${AUTH_REDIRECT_URI} + - AUTH_SILENT_REDIRECT_URI=${AUTH_SILENT_REDIRECT_URI} - USE_AUTH0=${USE_AUTH0} - LETSENCRYPT_DOMAIN=${LETSENCRYPT_DOMAIN} - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL} diff --git a/blueprints/netbird/template.toml b/blueprints/netbird/template.toml index aea86b866..f20e4743c 100644 --- a/blueprints/netbird/template.toml +++ b/blueprints/netbird/template.toml @@ -6,15 +6,17 @@ relay_port = "33080" relay_auth_secret = "${password:32}" turn_username = "${username}" turn_password = "${password:32}" -mgmt_api_endpoint = "https://${main_domain}:${mgmt_api_port}" -mgmt_grpc_api_endpoint = "https://${main_domain}:${mgmt_api_port}" +mgmt_api_endpoint = "https://${main_domain}" +mgmt_grpc_api_endpoint = "https://${main_domain}" dns_domain = "netbird.selfhosted" letsencrypt_domain = "${main_domain}" letsencrypt_email = "${email}" auth_client_id = "${uuid}" auth_client_secret = "${password:32}" -auth_authority = "https://${main_domain}" +auth_authority = "https://${main_domain}/oauth2" auth_audience = "${uuid}" +auth_redirect_uri = "https://${main_domain}/nb-auth" +auth_silent_redirect_uri = "https://${main_domain}/nb-silent-auth" mgmt_cert_file = "/etc/letsencrypt/live/${letsencrypt_domain}/fullchain.pem" mgmt_cert_key_file = "/etc/letsencrypt/live/${letsencrypt_domain}/privkey.pem" nb_listen_address = ":${relay_port}" @@ -48,6 +50,8 @@ env = [ "AUTH_CLIENT_ID=${auth_client_id}", "AUTH_CLIENT_SECRET=${auth_client_secret}", "AUTH_AUTHORITY=${auth_authority}", + "AUTH_REDIRECT_URI=${auth_redirect_uri}", + "AUTH_SILENT_REDIRECT_URI=${auth_silent_redirect_uri}", "USE_AUTH0=false", "LETSENCRYPT_DOMAIN=${letsencrypt_domain}", "LETSENCRYPT_EMAIL=${letsencrypt_email}", diff --git a/blueprints/pangolin/docker-compose.yml b/blueprints/pangolin/docker-compose.yml index d60ca5ed2..8e68f6d0c 100644 --- a/blueprints/pangolin/docker-compose.yml +++ b/blueprints/pangolin/docker-compose.yml @@ -7,11 +7,11 @@ services: volumes: - pangolin-config:/app/config healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"] - interval: 10s + test: ["CMD-SHELL", "pgrep -f 'node.*pangolin' || exit 1"] + interval: 30s timeout: 10s - retries: 15 - start_period: 30s + retries: 5 + start_period: 120s expose: - "3001" - "3002" @@ -21,7 +21,7 @@ services: restart: unless-stopped depends_on: pangolin: - condition: service_healthy + condition: service_started command: - --reachableAt=http://gerbil:3004 - --generateAndSaveKeyTo=/var/config/key @@ -43,7 +43,7 @@ services: restart: unless-stopped depends_on: pangolin: - condition: service_healthy + condition: service_started gerbil: condition: service_started command: From fa95411e171d466e3e4c6d81d85bfed20983694e Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Thu, 5 Feb 2026 08:34:03 -0600 Subject: [PATCH 4/9] Maybe these updates could help --- blueprints/netbird/docker-compose.yml | 5 +++++ blueprints/pangolin/docker-compose.yml | 14 +++++++------- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/blueprints/netbird/docker-compose.yml b/blueprints/netbird/docker-compose.yml index b15517c20..84ccd0489 100644 --- a/blueprints/netbird/docker-compose.yml +++ b/blueprints/netbird/docker-compose.yml @@ -5,6 +5,8 @@ services: image: ghcr.io/netbirdio/netbird:0.63.0-rootless restart: unless-stopped command: netbird-mgmt management + expose: + - "443" volumes: - netbird-management:/var/lib/netbird - netbird-ssl:/etc/letsencrypt:ro @@ -46,6 +48,8 @@ services: image: ghcr.io/netbirdio/netbird:0.63.0-rootless restart: unless-stopped command: netbird-dashboard + expose: + - "80" volumes: - netbird-ssl:/etc/letsencrypt:ro environment: @@ -66,6 +70,7 @@ services: coturn: image: coturn/coturn:latest restart: unless-stopped + network_mode: host command: -c /etc/turnserver.conf volumes: diff --git a/blueprints/pangolin/docker-compose.yml b/blueprints/pangolin/docker-compose.yml index 8e68f6d0c..99a1505c1 100644 --- a/blueprints/pangolin/docker-compose.yml +++ b/blueprints/pangolin/docker-compose.yml @@ -7,11 +7,11 @@ services: volumes: - pangolin-config:/app/config healthcheck: - test: ["CMD-SHELL", "pgrep -f 'node.*pangolin' || exit 1"] - interval: 30s - timeout: 10s - retries: 5 - start_period: 120s + test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"] + interval: 3s + timeout: 3s + retries: 15 + start_period: 90s expose: - "3001" - "3002" @@ -21,7 +21,7 @@ services: restart: unless-stopped depends_on: pangolin: - condition: service_started + condition: service_healthy command: - --reachableAt=http://gerbil:3004 - --generateAndSaveKeyTo=/var/config/key @@ -43,7 +43,7 @@ services: restart: unless-stopped depends_on: pangolin: - condition: service_started + condition: service_healthy gerbil: condition: service_started command: From a563cf82cf27d23c1753c96e4ef6626358559796 Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Thu, 5 Feb 2026 08:35:21 -0600 Subject: [PATCH 5/9] Add README for netbird (I can remove the READMEs later if necessary) --- blueprints/netbird/README.md | 44 ++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 blueprints/netbird/README.md diff --git a/blueprints/netbird/README.md b/blueprints/netbird/README.md new file mode 100644 index 000000000..6d34b31f8 --- /dev/null +++ b/blueprints/netbird/README.md @@ -0,0 +1,44 @@ +# Netbird Template for Dokploy + +## Overview + +Netbird is a WireGuard-based mesh VPN and zero-trust networking platform. This template deploys the Netbird management server, signal server, relay, dashboard, and Coturn (TURN server) for use with Dokploy. + +## Services + +- **netbird-management** – Management API and configuration (HTTPS, port 443) +- **netbird-signal** – Signal exchange server for peer coordination (port 10000) +- **netbird-relay** – Relay service for peer connections (port 33080) +- **netbird-dashboard** – Web UI (port 80) +- **coturn** – TURN server for NAT traversal (runs with `network_mode: host`; uses ports 3478, 5349, and UDP relay range 49152–65535) + +## Domain routing + +Configure a domain in Dokploy for this deployment. The template assigns: + +- **netbird-dashboard** (port 80) – Dashboard UI +- **netbird-management** (port 443) – Management API + +Traefik will route traffic to these services by hostname. + +## Signal and Relay ports (client connectivity) + +Netbird **clients** (desktop, mobile, or other hosts) must be able to reach: + +- **Signal:** port **10000** (TCP) +- **Relay:** port **33080** (TCP) + +When deploying via Dokploy, these ports are only exposed on the internal Docker network by default. For clients outside the host to connect: + +1. **Dokploy / host:** Expose ports 10000 and 33080 on the host (e.g. via Dokploy port settings, firewall rules, or a reverse proxy that forwards TCP to these services), or +2. Ensure your deployment environment (e.g. cloud security groups, firewall) allows traffic to the host on 10000 and 33080 and that your compose/stack publishes these ports if you are not using Dokploy’s domain-only routing. + +Without Signal and Relay reachable from client networks, peers will not be able to establish connections. + +## Coturn (TURN) + +The **coturn** service runs with `network_mode: host` so the TURN relay can use the full UDP port range (49152–65535) and behave correctly for NAT traversal. No other TURN/STUN service should use the same ports on the host. + +## Configuration + +Set the required variables in the Dokploy deployment (domain, Let’s Encrypt email, auth client ID/secret, relay auth secret, TURN username/password, etc.) as defined in `template.toml`. From 004588b9834ed4e45222390d21c113c95a456b37 Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Thu, 5 Feb 2026 10:08:59 -0600 Subject: [PATCH 6/9] Healthcheck fix for Pangolin --- blueprints/pangolin/README.md | 1 + blueprints/pangolin/docker-compose.yml | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/blueprints/pangolin/README.md b/blueprints/pangolin/README.md index 5ccfc7a51..09b1a7afd 100644 --- a/blueprints/pangolin/README.md +++ b/blueprints/pangolin/README.md @@ -856,6 +856,7 @@ curl -I https://your-pangolin-domain.com 4. Verify CORS configuration if needed **Common issues:** +- **Pangolin container unhealthy**: Startup can take 2+ minutes (DB init, config load). Check pangolin container logs for errors; ensure config is mounted at `/app/config/config.yml`. If the app fails to bind to port 3001, the healthcheck will keep failing. - API not responding: Check pangolin healthcheck - Routing misconfiguration: Verify Traefik dynamic config - CORS errors: Check browser console for details diff --git a/blueprints/pangolin/docker-compose.yml b/blueprints/pangolin/docker-compose.yml index 99a1505c1..7ab68e902 100644 --- a/blueprints/pangolin/docker-compose.yml +++ b/blueprints/pangolin/docker-compose.yml @@ -7,11 +7,11 @@ services: volumes: - pangolin-config:/app/config healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"] - interval: 3s - timeout: 3s - retries: 15 - start_period: 90s + test: ["CMD", "curl", "-f", "--connect-timeout", "2", "--max-time", "5", "http://127.0.0.1:3001/api/v1/"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 120s expose: - "3001" - "3002" From a7d29dac21a64ade845232754ce52fb40067ab82 Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Thu, 5 Feb 2026 10:17:14 -0600 Subject: [PATCH 7/9] Fix another problem --- blueprints/pangolin/template.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/blueprints/pangolin/template.toml b/blueprints/pangolin/template.toml index ec1f8a525..f6bc5f5b5 100644 --- a/blueprints/pangolin/template.toml +++ b/blueprints/pangolin/template.toml @@ -13,6 +13,7 @@ host = "${main_domain}" SERVER_SECRET = "${pangolin_secret}" [[config.mounts]] +serviceName = "pangolin" filePath = "/app/config/config.yml" content = """ app: @@ -73,6 +74,7 @@ flags: """ [[config.mounts]] +serviceName = "traefik" filePath = "/etc/traefik/traefik_config.yml" content = """ api: @@ -124,6 +126,7 @@ ping: """ [[config.mounts]] +serviceName = "traefik" filePath = "/etc/traefik/dynamic_config.yml" content = """ http: From 1fd2f9ed4e4f2fad529183d90c820533cd32011a Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Thu, 5 Feb 2026 10:28:31 -0600 Subject: [PATCH 8/9] Another fix --- blueprints/pangolin/README.md | 2 +- blueprints/pangolin/docker-compose.yml | 6 +++ blueprints/pangolin/template.toml | 64 ++------------------------ 3 files changed, 10 insertions(+), 62 deletions(-) diff --git a/blueprints/pangolin/README.md b/blueprints/pangolin/README.md index 09b1a7afd..a5e6f84b9 100644 --- a/blueprints/pangolin/README.md +++ b/blueprints/pangolin/README.md @@ -57,7 +57,7 @@ mylocalapp.example.com → Dokploy → Pangolin → Traefik → Local Resource The template automatically configures: -- **Pangolin config** (`/app/config/config.yml`) - Main application configuration +- **Pangolin config** (`/app/config/config.yml`) - Written at startup from the `PANGOLIN_CONFIG` environment variable (so it works even when Dokploy mounts config files only on one service). The container runs a small wrapper that writes the YAML to disk then starts the app. - **Traefik static config** (`/etc/traefik/traefik_config.yml`) - Traefik entrypoints and certificate resolver - **Traefik dynamic config** (`/etc/traefik/dynamic_config.yml`) - Routing rules for dashboard and API - **SSL certificates** - Managed via Let's Encrypt diff --git a/blueprints/pangolin/docker-compose.yml b/blueprints/pangolin/docker-compose.yml index 7ab68e902..eaa5fc27c 100644 --- a/blueprints/pangolin/docker-compose.yml +++ b/blueprints/pangolin/docker-compose.yml @@ -4,6 +4,12 @@ services: pangolin: image: fosrl/pangolin:latest restart: unless-stopped + env_file: + - .env + command: + - sh + - -c + - 'mkdir -p /app/config && printf "%b" "$$PANGOLIN_CONFIG" > /app/config/config.yml && exec npm run start' volumes: - pangolin-config:/app/config healthcheck: diff --git a/blueprints/pangolin/template.toml b/blueprints/pangolin/template.toml index f6bc5f5b5..33a75fef1 100644 --- a/blueprints/pangolin/template.toml +++ b/blueprints/pangolin/template.toml @@ -9,69 +9,11 @@ serviceName = "traefik" port = 80 host = "${main_domain}" +# PANGOLIN_CONFIG: single-line YAML with \n for newlines; pangolin writes it to /app/config/config.yml at startup. +# This works even when Dokploy mounts config files only on one service (e.g. traefik). [config.env] SERVER_SECRET = "${pangolin_secret}" - -[[config.mounts]] -serviceName = "pangolin" -filePath = "/app/config/config.yml" -content = """ -app: - dashboard_url: "https://${main_domain}" - log_level: "info" - -domains: - domain1: - base_domain: "${main_domain}" - cert_resolver: "letsencrypt" - prefer_wildcard_cert: false - -server: - integration_port: 3003 - external_port: 3000 - internal_port: 3001 - next_port: 3002 - internal_hostname: "pangolin" - session_cookie_name: "p_session_token" - resource_access_token_param: "p_token" - resource_access_token_headers: - id: "P-Access-Token-Id" - token: "P-Access-Token" - dashboard_session_length_hours: 720 - resource_session_length_hours: 720 - trust_proxy: 1 - secret: "${pangolin_secret}" - -gerbil: - base_endpoint: "${gerbil_endpoint}" - use_subnet: false - subnet_group: "100.89.137.0/20" - block_size: 24 - site_block_size: 30 - -orgs: - block_size: 24 - subnet_group: "100.90.128.0/20" - utility_subnet_group: "100.96.128.0/20" - -traefik: - http_entrypoint: "web" - https_entrypoint: "websecure" - cert_resolver: "letsencrypt" - prefer_wildcard_cert: false - file_mode: true - site_types: ["newt", "wireguard", "local"] - allow_raw_resources: true - -flags: - require_email_verification: false - disable_signup_without_invite: false - disable_user_create_org: false - allow_raw_resources: false - enable_integration_api: false - disable_local_sites: false - disable_basic_wireguard_sites: false -""" +PANGOLIN_CONFIG = "app:\n dashboard_url: \"https://${main_domain}\"\n log_level: \"info\"\n\ndomains:\n domain1:\n base_domain: \"${main_domain}\"\n cert_resolver: \"letsencrypt\"\n prefer_wildcard_cert: false\n\nserver:\n integration_port: 3003\n external_port: 3000\n internal_port: 3001\n next_port: 3002\n internal_hostname: \"pangolin\"\n session_cookie_name: \"p_session_token\"\n resource_access_token_param: \"p_token\"\n resource_access_token_headers:\n id: \"P-Access-Token-Id\"\n token: \"P-Access-Token\"\n dashboard_session_length_hours: 720\n resource_session_length_hours: 720\n trust_proxy: 1\n secret: \"${pangolin_secret}\"\n\ngerbil:\n base_endpoint: \"${gerbil_endpoint}\"\n use_subnet: false\n subnet_group: \"100.89.137.0/20\"\n block_size: 24\n site_block_size: 30\n\norgs:\n block_size: 24\n subnet_group: \"100.90.128.0/20\"\n utility_subnet_group: \"100.96.128.0/20\"\n\ntraefik:\n http_entrypoint: \"web\"\n https_entrypoint: \"websecure\"\n cert_resolver: \"letsencrypt\"\n prefer_wildcard_cert: false\n file_mode: true\n site_types: [\"newt\", \"wireguard\", \"local\"]\n allow_raw_resources: true\n\nflags:\n require_email_verification: false\n disable_signup_without_invite: false\n disable_user_create_org: false\n allow_raw_resources: false\n enable_integration_api: false\n disable_local_sites: false\n disable_basic_wireguard_sites: false" [[config.mounts]] serviceName = "traefik" From 649b18aaf77a1956b7401f93bf61d0af12560c8d Mon Sep 17 00:00:00 2001 From: Christopher Kapic Date: Thu, 5 Feb 2026 10:32:42 -0600 Subject: [PATCH 9/9] Another fix --- blueprints/pangolin/template.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/blueprints/pangolin/template.toml b/blueprints/pangolin/template.toml index 33a75fef1..83100180d 100644 --- a/blueprints/pangolin/template.toml +++ b/blueprints/pangolin/template.toml @@ -13,7 +13,8 @@ host = "${main_domain}" # This works even when Dokploy mounts config files only on one service (e.g. traefik). [config.env] SERVER_SECRET = "${pangolin_secret}" -PANGOLIN_CONFIG = "app:\n dashboard_url: \"https://${main_domain}\"\n log_level: \"info\"\n\ndomains:\n domain1:\n base_domain: \"${main_domain}\"\n cert_resolver: \"letsencrypt\"\n prefer_wildcard_cert: false\n\nserver:\n integration_port: 3003\n external_port: 3000\n internal_port: 3001\n next_port: 3002\n internal_hostname: \"pangolin\"\n session_cookie_name: \"p_session_token\"\n resource_access_token_param: \"p_token\"\n resource_access_token_headers:\n id: \"P-Access-Token-Id\"\n token: \"P-Access-Token\"\n dashboard_session_length_hours: 720\n resource_session_length_hours: 720\n trust_proxy: 1\n secret: \"${pangolin_secret}\"\n\ngerbil:\n base_endpoint: \"${gerbil_endpoint}\"\n use_subnet: false\n subnet_group: \"100.89.137.0/20\"\n block_size: 24\n site_block_size: 30\n\norgs:\n block_size: 24\n subnet_group: \"100.90.128.0/20\"\n utility_subnet_group: \"100.96.128.0/20\"\n\ntraefik:\n http_entrypoint: \"web\"\n https_entrypoint: \"websecure\"\n cert_resolver: \"letsencrypt\"\n prefer_wildcard_cert: false\n file_mode: true\n site_types: [\"newt\", \"wireguard\", \"local\"]\n allow_raw_resources: true\n\nflags:\n require_email_verification: false\n disable_signup_without_invite: false\n disable_user_create_org: false\n allow_raw_resources: false\n enable_integration_api: false\n disable_local_sites: false\n disable_basic_wireguard_sites: false" +# Single-quoted YAML so .env is not broken by double quotes; \n for newlines. +PANGOLIN_CONFIG = "app:\n dashboard_url: 'https://${main_domain}'\n log_level: 'info'\n\ndomains:\n domain1:\n base_domain: '${main_domain}'\n cert_resolver: 'letsencrypt'\n prefer_wildcard_cert: false\n\nserver:\n integration_port: 3003\n external_port: 3000\n internal_port: 3001\n next_port: 3002\n internal_hostname: 'pangolin'\n session_cookie_name: 'p_session_token'\n resource_access_token_param: 'p_token'\n resource_access_token_headers:\n id: 'P-Access-Token-Id'\n token: 'P-Access-Token'\n dashboard_session_length_hours: 720\n resource_session_length_hours: 720\n trust_proxy: 1\n secret: '${pangolin_secret}'\n\ngerbil:\n base_endpoint: '${gerbil_endpoint}'\n use_subnet: false\n subnet_group: '100.89.137.0/20'\n block_size: 24\n site_block_size: 30\n\norgs:\n block_size: 24\n subnet_group: '100.90.128.0/20'\n utility_subnet_group: '100.96.128.0/20'\n\ntraefik:\n http_entrypoint: 'web'\n https_entrypoint: 'websecure'\n cert_resolver: 'letsencrypt'\n prefer_wildcard_cert: false\n file_mode: true\n site_types: ['newt', 'wireguard', 'local']\n allow_raw_resources: true\n\nflags:\n require_email_verification: false\n disable_signup_without_invite: false\n disable_user_create_org: false\n allow_raw_resources: false\n enable_integration_api: false\n disable_local_sites: false\n disable_basic_wireguard_sites: false" [[config.mounts]] serviceName = "traefik"