WireGuard VPN container with JWT-authenticated peer management API for the NABU VPN service.
This container provides a WireGuard VPN server with a REST API for dynamic peer management. The API accepts JWT-authenticated requests from the vpn-indexer to add and remove WireGuard peers on-the-fly.
docker run -d \
--name wireguard \
--cap-add=NET_ADMIN \
-e WG_ENDPOINT=vpn.example.com:51820 \
-e WG_PRIVATE_KEY=$(wg genkey) \
-v /path/to/jwt-verify.pub:/etc/wireguard/jwt-verify.pub:ro \
-p 51820:51820/udp \
-p 8080:8080 \
ghcr.io/blinklabs-io/docker-wireguard:latest| Variable | Default | Description |
|---|---|---|
WG_PRIVATE_KEY |
(generated) | Server private key (base64). If not provided, an ephemeral key is generated. |
WG_PORT |
51820 |
WireGuard UDP listen port |
WG_SUBNET |
10.8.0.1/24 |
Server address and subnet |
WG_ENDPOINT |
(required) | Public endpoint for clients (e.g., vpn.example.com:51820) |
NAT_DEVICE |
eth0 |
Outbound NAT interface |
API_LISTEN |
:8080 |
Peer API listen address |
JWT_PUBLIC_KEY_FILE |
/etc/wireguard/jwt-verify.pub |
Path to Ed25519 public key for JWT verification |
ENABLE_NAT |
1 |
Enable NAT masquerading (set to 0 to disable) |
DEBUG |
0 |
Enable debug logging |
USER_STARTUP_SCRIPT |
/usr/local/bin/entrypoint-user.sh |
Optional user startup script |
GET /health
Returns the health status of the API.
Response:
{"status": "healthy"}GET /info
Returns the server's public key and endpoint.
Response:
{
"server_pubkey": "base64_encoded_public_key",
"endpoint": "vpn.example.com:51820"
}POST /peer
Content-Type: application/json
{
"jwt": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
"pubkey": "client_wireguard_public_key"
}
Adds a WireGuard peer. Requires a valid JWT token.
Response:
{
"success": true,
"server_pubkey": "base64_encoded_public_key",
"endpoint": "vpn.example.com:51820",
"allowed_ips": "0.0.0.0/0"
}DELETE /peer
Content-Type: application/json
{
"jwt": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...",
"pubkey": "client_wireguard_public_key"
}
Removes a WireGuard peer. Requires a valid JWT token.
Response:
{"success": true}GET /metrics
Returns Prometheus-formatted metrics for monitoring.
Available Metrics:
wg_peers_added_total- Total number of WireGuard peers addedwg_peers_removed_total- Total number of WireGuard peers removedwg_active_peers- Current number of active WireGuard peerswg_peer_operation_errors_total- Peer operation errors by type (add/remove)wg_api_requests_total- API requests by endpoint, method, and status codewg_api_request_duration_seconds- API request duration histogramwg_jwt_validation_errors_total- JWT validation errors
JWTs must be:
- Signed with Ed25519 (EdDSA algorithm)
- Have
subclaim set towg_peer - Include
pubkeyclaim matching the request pubkey - Include
allowed_ipclaim with the client's assigned IP - Have
exp(expiration) claim set (recommended: 60 seconds from issuance)
Example JWT Claims:
{
"sub": "wg_peer",
"pubkey": "client_wireguard_public_key",
"allowed_ip": "10.8.0.42",
"exp": 1705123516,
"iat": 1705123456
}- Container requires
NET_ADMINcapability for WireGuard kernel operations - Private keys should be stored in secrets, never logged
- Default firewall policy is DROP; only forwarded traffic from wg0 is allowed
- API mutations require valid, non-expired JWT
- Network policies should restrict API access to trusted services only
# Build Docker image
docker build -t wireguard:latest .
# Build Go binary locally
go build -o wg-peer-api ./cmd/wg-peer-api
# Run tests
go test ./...See the vpn-infrastructure repository for Helm charts and deployment configurations.
Copyright 2026 Blink Labs Software
Licensed under the Apache License, Version 2.0. See LICENSE for details.