Dokploy Hook Pangolin
A webhook service that automatically registers new Dokploy applications with Pangolin VPN, creating resources and targets for seamless integration.
Overview
This service acts as a bridge between Dokploy (deployment platform) and Pangolin VPN (VPN/tunneling service). When Dokploy deploys a new application, it sends a webhook to this service, which automatically:
- Receives build notifications from Dokploy
- Checks if the domain already exists in Pangolin
- Creates a new Pangolin resource if needed
- Configures a resource target pointing to your main site
- Enables automatic routing through Pangolin VPN
Architecture
┌──────────┐ ┌─────────────────┐ ┌──────────┐
│ Dokploy │────────▶│ This Service │────────▶│ Pangolin │
│ │ Webhook │ │ API │ VPN │
└──────────┘ └─────────────────┘ └──────────┘
│
│ Validates
▼
┌──────────────┐
│ Config │
│ Environment │
└──────────────┘Features
- Automatic resource creation in Pangolin when new domains are deployed
- Intelligent domain matching to avoid duplicates
- Duplicate detection and cleanup for resources that already got cloned
- Webhook authentication with secret token
- Comprehensive error handling and logging
- Graceful shutdown handling (SIGINT/SIGTERM)
- Full TypeScript support with custom types
- 90%+ test coverage
- Docker & Docker Compose support
- Built with Bun & Hono for performance
Prerequisites
- A Pangolin account or self-hosted instance with API access
- A Dokploy instance
- Docker
Deployment
IMPORTANT: This service must be accessible by Dokploy to receive webhooks. You can deploy it through Dokploy & Pangolin with no issues, but if you want to run it separately, ensure it's reachable at the configured URL.
With Docker Run
docker run -p 3000:3000 \
-e WEBHOOK_SECRET=your-secret \
-e PANGOLIN_API_KEY=your-key \
-e PANGOLIN_ORG_ID=your-org \
-e PANGOLIN_MAIN_SITE_NAME=your-site \
-e PANGOLIN_API_BASE_URL=https://api.pangolin.com \
orochibraru/dokploy-to-pangolinWith Docker Compose
- Create a
compose.ymlfile:
services:
hook:
image: orochibraru/dokploy-to-pangolin:latest
ports:
- "3000:3000"
environment:
- PORT=3000
- WEBHOOK_SECRET=${WEBHOOK_SECRET}
- PANGOLIN_API_KEY=${PANGOLIN_API_KEY}
- PANGOLIN_ORG_ID=${PANGOLIN_ORG_ID}
- PANGOLIN_MAIN_SITE_NAME=${PANGOLIN_MAIN_SITE_NAME}
- PANGOLIN_API_BASE_URL=${PANGOLIN_API_BASE_URL}Configuration
Configure the following environment variables in your .env file:
| Variable | Description | Example |
|---|---|---|
PANGOLIN_API_KEY | Your Pangolin API key | pk_live_abc123... |
PANGOLIN_ORG_ID | Your Pangolin organization ID | org_xyz789 |
PANGOLIN_MAIN_SITE_NAME | Name of your main Pangolin site | production-site |
PANGOLIN_API_BASE_URL | Pangolin API base URL | https://api.pangolin.com |
WEBHOOK_SECRET | Secret token for webhook authentication | your-secure-secret-here |
PORT | Server port (optional, default: 3000) | 3000 |
Getting Pangolin Credentials
- If self-hosting, ensure the API integration is setup correctly
- Log in to your Pangolin dashboard
- Navigate to API settings
- Generate a new API key
- Note your organization ID from the URL or settings
Configuring Dokploy
- In your Dokploy project settings, navigate to Webhooks
- Add a new webhook:
- URL:
http://your-service-url:3000/webhook - Headers: Set
x-webhook-secretto your configuredWEBHOOK_SECRETenvironment variable - Actions: Select "App Deploy" events
- URL:
- Save and deploy your application
- Check the logs to verify webhook processing
API Endpoints
GET /
Health check endpoint
Response:
OKPOST /webhook
Webhook endpoint for Dokploy events
Headers:
x-webhook-secret: Must matchWEBHOOK_SECRETenvironment variable
Request Body:
{
"title": "Build Success",
"message": "Application deployed successfully",
"timestamp": "2026-03-03T12:00:00Z",
"type": "build",
"status": "success",
"projectName": "my-app",
"domains": "my-app.example.com"
}Response:
200 OK: Webhook processed successfully401 Unauthorized: Invalid webhook secret500 Internal Server Error: Processing error
GET /duplicates
Reports resources that share a host name, without changing anything.
Headers:
x-webhook-secret: Must matchWEBHOOK_SECRETenvironment variable
Response:
{
"scanned": 256,
"applied": false,
"deleted": 0,
"renamed": [],
"failed": 0,
"groups": [
{
"fullDomain": "app.example.com",
"keep": { "resourceId": 61, "name": "my-app" },
"duplicates": [{ "resourceId": 91, "name": "my-app" }]
}
]
}DELETE /duplicates
Same report, but deletes the duplicates and tidies up the names that survive. See Duplicate cleanup.
Duplicate cleanup
Two resources sharing a host name are a routing conflict: Pangolin serves whichever one it happens to pick. The oldest resource in each group is kept, because it is the one that was configured deliberately - its SSO, auth and rule settings are the ones you set - and the later copies are deleted.
Review first, then apply:
# Report what would change
bun run reconcile
# Delete the duplicates and tidy up the surviving names
bun run reconcile --apply
# Delete the duplicates but leave names alone
bun run reconcile --apply --no-renameRenaming collapses repeated words in a name, so
sergios-sergios-sergios becomes sergios. It has only the existing name to
work from, so it would also shorten a deliberate new-project-new-app - which
is why the dry run comes first.
The service logs a warning at startup when duplicates exist, but never deletes anything on its own.
How It Works
Workflow
-
Dokploy Deploy Event
- User deploys an application in Dokploy
- Dokploy sends a webhook to
/webhookendpoint
-
Webhook Validation
- Service validates the webhook secret
- Checks if the event type is
build - Verifies build status is not
error
-
Domain Check
- Fetches existing Pangolin resources
- Checks if domain already exists
-
Resource Creation (if needed)
- Extracts subdomain from full domain
- Creates a new Pangolin resource
- Configures HTTP routing and sticky sessions
-
Target Setup
- Creates a resource target pointing to main site
- Configures HTTPS on port 443
- Enables the target
Example Flow
1. Deploy "api-service" with domain "api.example.com"
↓
2. Webhook received with domains: "api.example.com"
↓
3. Check Pangolin resources
↓
4. No match found → Create new resource
- Name: "api-service"
- Subdomain: "api"
- Domain: "example.com"
↓
5. Create resource target
- Site: main-site
- Port: 443
- Method: HTTPS
↓
6. ✅ "api.example.com" now routes through PangolinTroubleshooting
Common Issues
"Unauthorized webhook attempt detected"
- Ensure
WEBHOOK_SECRETmatches in both Dokploy and this service - Check the
x-webhook-secretheader is being sent
"Cannot create resource without main domain"
- Ensure the domain exists in your Pangolin instance/account
"No subdomain extracted from event domains"
- Domain format should be:
subdomain.maindomain.com
Duplicate resources keep appearing for the same domain
- Fixed: the resource listing now pages through the whole collection. The API defaults to 20 items per page, so the service used to miss anything past the first page and re-create resources it already had
- Events are also handled one at a time, so two overlapping deploys can no longer both decide a resource is missing
- Run
bun run reconcileto clean up copies made before the fix
Type errors about "never" types
- This is expected - the Pangolin OpenAPI spec is poorly defined
- We use custom types in
src/lib/types.tswith@ts-expect-errorcomments
Security Considerations
- Store sensitive credentials in environment variables, never commit them
- Use a strong, random
WEBHOOK_SECRET(openssl rand -hex 32for example) - Consider using HTTPS in production (reverse proxy recommended)
- Rotate API keys periodically
- Limit network access to webhook endpoint if possible
Development
Getting started
- Clone the repository:
git clone https://github.com/orochibraru/dokploy-to-pangolin.git
cd dokploy-to-pangolin- Install dependencies:
bun install- Copy the example environment file:
cp .example.env .env-
Configure your environment variables (see Configuration)
-
Start the development server:
bun run devAvailable Scripts
# Start development server with auto-reload
bun run dev
# Build production bundle
bun run build
# Start production server
bun run start
# Run tests
bun test
# Lint and format code
bun run lint
# Auto-fix linting issues
bun run lint:fix
# Type check
bun run check
# Generate Pangolin API types (from OpenAPI spec)
bun run gen:api
# Report duplicate resources (add --apply to clean them up)
bun run reconcileTesting
The project has comprehensive test coverage (98.93%):
# Run all tests
bun test
# Run tests with coverage
bun test --coverageTech Stack
- Runtime: Bun - Fast JavaScript runtime
- Framework: Hono - Lightweight web framework
- API Client: openapi-fetch - Type-safe API client
- Linter: Biome - Fast linter and formatter
- Testing: Bun's built-in test runner
- Container: Docker with Alpine Linux
A note on AI generated code
Some code in this project was generated with the help of AI tools. This only concerns tests and markdown (which is already bad enough). The core logic and implementation were written by hand. Please follow this pattern when contributing to ensure code quality and consistency, to ensure we don't mutually destroy our Homelabs ❤️
Contributing
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes and add tests
- Run tests:
bun test - Run linter:
bun run lint:fix - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
Support
For issues or questions:
- Open an issue on GitHub
- Check the troubleshooting section
- Review Pangolin API documentation
Built with ❤️ using Bun and TypeScript
This guide lives in the project repo: edit it there, and this page follows within a day.