A lightweight Go service that watches Docker container events and automatically provisions/removes Pangolin reverse-proxy resources — so you never have to manually configure a route in the Pangolin UI.
Pangolin's native Docker label feature (Blueprints) requires Newt to run as a Docker container with the socket mounted. On Windows, Newt typically runs as a Windows service and cannot read Docker labels.
pangolin-companion runs as a Docker container inside Docker Desktop, listens to the Docker Engine socket, and provisions Pangolin routes automatically via the Integration API.
It is a companion to pangolin-dns, which handles DNS resolution for the same Pangolin routes.
Docker Socket (named pipe on Windows)
│ events: start / die
▼
┌──────────────────┐
│ Event Watcher │ streams Docker events, filters pangolin.enable=true
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Label Parser │ reads pangolin.* labels via ContainerInspect
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Reconciler │ deduplicates Compose replicas, persists state
└────────┬─────────┘
│ create / delete
▼
┌──────────────────┐
│ Pangolin Client │ Integration API: PUT resource+target, DELETE resource
└──────────────────┘
- On startup — scans all running containers and creates any missing resources.
- On
container start— creates the Pangolin resource and target. - On
container die— deletes the resource (ifCLEANUP_ON_STOP=true). - Compose dedup — multiple replicas of the same service share one resource.
- Copy
docker-compose.ymlfrom this repo and create a.envfile:
PANGOLIN_API_KEY=keyId.keySecret
PANGOLIN_ORG_ID=your-org-id
PANGOLIN_SITE_ID=your-site-id
PANGOLIN_DOMAIN_ID=your-domain-id- Start:
docker compose up -dThe service listens on port 8081 (mapped to internal 8080).
Linux users: replace the
volumessection indocker-compose.yml— see the commented alternative at the bottom of the file.
services:
myapp:
image: myapp:latest
labels:
pangolin.enable: "true"
pangolin.name: "My App"
pangolin.subdomain: "app" # → app.<your-base-domain>
pangolin.target.port: "8080"That's it. When myapp starts, pangolin-companion creates the route in Pangolin
and tears it down when the container stops.
| Label | Required | Default | Description |
|---|---|---|---|
pangolin.enable |
yes | — | Must be "true" to opt in |
pangolin.name |
yes | — | Display name in Pangolin UI |
pangolin.subdomain |
yes | — | Subdomain prefix (e.g. app → app.example.com) |
pangolin.target.port |
yes | — | Container port to proxy to |
pangolin.site-id |
no | PANGOLIN_SITE_ID |
Override default site |
pangolin.domain-id |
no | PANGOLIN_DOMAIN_ID |
Override default domain |
pangolin.ssl |
no | false |
TLS to backend |
pangolin.sso |
no | false |
Pangolin SSO gate |
| Variable | Default | Required | Description |
|---|---|---|---|
PANGOLIN_API_KEY |
— | yes | API key (keyId.keySecret) |
PANGOLIN_ORG_ID |
— | yes | Pangolin organization ID |
PANGOLIN_SITE_ID |
— | yes | Default site ID for new resources |
PANGOLIN_DOMAIN_ID |
— | yes | Default domain ID for new resources |
PANGOLIN_API_URL |
http://10.1.100.2:3003 |
no | Integration API base URL |
CLEANUP_ON_STOP |
true |
no | Delete resource when container stops |
STATE_FILE |
/data/state.json |
no | Path to persistent state JSON |
HEALTH_PORT |
8080 |
no | HTTP health endpoint port |
GET /healthz → { "status": "ok", "managed_resources": 3 }
GET /status → { "resources": [{ "container_id": "...", "resource_id": "...", "subdomain": "app" }] }
git clone https://github.com/TimElschner/pangolin-companion
cd pangolin-companion
go build -o pangolin-companion .
PANGOLIN_API_KEY=id.secret PANGOLIN_ORG_ID=org1 \
PANGOLIN_SITE_ID=site1 PANGOLIN_DOMAIN_ID=dom1 \
./pangolin-companiongo test -v ./...docker pull timelschner/pangolin-companion:latestMIT