Files
netbird/infrastructure_files/docker-compose.yml.tmpl.traefik
Claude e3c23c263b relay: deploy templates expose UDP, mgmt threads transport hints
Two follow-ups to the WebTransport/ALPN-mux landing:

Deployment templates publish UDP alongside TCP for the relay so the
single ALPN-multiplexed socket can serve raw QUIC and WebTransport
clients on the same port as the existing WebSocket transport.

- docker-compose.yml.tmpl: adds the matching `/udp` mapping; the relay
  was already binding both stacks, the host port just wasn't published.
- docker-compose.yml.tmpl.traefik: WebTransport is the awkward case —
  Traefik can't proxy WT sessions, so the relay container now publishes
  UDP/443 directly and obtains its own Let's Encrypt cert (separate
  volume), while the TCP /relay route stays behind Traefik unchanged so
  WS-only clients keep working.

Management server learned to advertise per-relay transport hints:

- Config gains an optional `Endpoints []{URL, Transports}` block on the
  Relay section, mirrored to clients as RelayConfig.endpoints.
- `Addresses` is still emitted as RelayConfig.urls so older agents keep
  working unchanged.
- A single BuildRelayConfigProto helper is the only place that builds
  the proto, called from both toNetbirdConfig and the token push paths.

The GeoDNS case is operator-asserted, not probed: a single URL fans out
to several physical relays, and the Transports list must already be the
intersection of what every backend supports. Documented on the config
struct — if any backend behind a hostname can't speak h3, the operator
drops "wt" from that hostname's list and no client tries it there.
2026-05-17 11:21:38 +00:00

150 lines
6.0 KiB
Plaintext

x-default: &default
restart: 'unless-stopped'
logging:
driver: 'json-file'
options:
max-size: '500m'
max-file: '2'
services:
# UI dashboard
dashboard:
<<: *default
image: netbirdio/dashboard:$NETBIRD_DASHBOARD_TAG
environment:
# Endpoints
- NETBIRD_MGMT_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
- NETBIRD_MGMT_GRPC_API_ENDPOINT=$NETBIRD_MGMT_API_ENDPOINT
# OIDC
- AUTH_AUDIENCE=$NETBIRD_DASH_AUTH_AUDIENCE
- AUTH_CLIENT_ID=$NETBIRD_AUTH_CLIENT_ID
- AUTH_CLIENT_SECRET=$NETBIRD_AUTH_CLIENT_SECRET
- AUTH_AUTHORITY=$NETBIRD_AUTH_AUTHORITY
- USE_AUTH0=$NETBIRD_USE_AUTH0
- AUTH_SUPPORTED_SCOPES=$NETBIRD_AUTH_SUPPORTED_SCOPES
- AUTH_REDIRECT_URI=$NETBIRD_AUTH_REDIRECT_URI
- AUTH_SILENT_REDIRECT_URI=$NETBIRD_AUTH_SILENT_REDIRECT_URI
- NETBIRD_TOKEN_SOURCE=$NETBIRD_TOKEN_SOURCE
# SSL
- NGINX_SSL_PORT=443
# Letsencrypt
- LETSENCRYPT_DOMAIN=$NETBIRD_LETSENCRYPT_DOMAIN
- LETSENCRYPT_EMAIL=$NETBIRD_LETSENCRYPT_EMAIL
volumes:
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt/
labels:
- traefik.enable=true
- traefik.http.routers.netbird-dashboard.rule=Host(`$NETBIRD_DOMAIN`)
- traefik.http.services.netbird-dashboard.loadbalancer.server.port=80
# Signal
signal:
<<: *default
image: netbirdio/signal:$NETBIRD_SIGNAL_TAG
volumes:
- $SIGNAL_VOLUMENAME:/var/lib/netbird
labels:
- traefik.enable=true
- traefik.http.routers.netbird-wsproxy-signal.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/ws-proxy/signal`)
- traefik.http.routers.netbird-wsproxy-signal.service=netbird-wsproxy-signal
- traefik.http.services.netbird-wsproxy-signal.loadbalancer.server.port=80
- traefik.http.routers.netbird-signal.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/signalexchange.SignalExchange/`)
- traefik.http.routers.netbird-signal.service=netbird-signal
- traefik.http.services.netbird-signal.loadbalancer.server.port=10000
- traefik.http.services.netbird-signal.loadbalancer.server.scheme=h2c
# Relay
#
# Traefik fronts the TCP/WebSocket side of the relay on port 443 via the HTTP
# router below — this gives us WS over TLS that traverses any HTTP proxy.
#
# WebTransport (h3) and raw QUIC require direct UDP termination on the relay
# itself: Traefik does not proxy WebTransport sessions, and tunnelling the
# h3 stream end-to-end through a reverse proxy defeats the point. The relay
# therefore publishes UDP/443 on the host directly and terminates TLS for
# both ALPNs ("nb-quic" and "h3") on a single socket via ALPN multiplexing.
#
# The relay obtains its own Let's Encrypt certificate (NB_LETSENCRYPT_*
# below) since Traefik's certificate store is not shared with the container.
# The cert is bound to NETBIRD_RELAY_DOMAIN — point this at the same FQDN
# clients use to dial the relay.
#
# If a deployment can't open UDP/443 to the host (firewall, k8s without
# hostPort, etc.), leave it unmapped: native clients fall back to raw QUIC
# over WS and browser clients fall back to WS. Drop "quic"/"wt" from the
# management Relays config in that case so clients don't waste a handshake.
relay:
<<: *default
image: netbirdio/relay:$NETBIRD_RELAY_TAG
environment:
- NB_LOG_LEVEL=info
- NB_LISTEN_ADDRESS=:443
- NB_EXPOSED_ADDRESS=$NETBIRD_RELAY_ENDPOINT
- NB_LETSENCRYPT_DOMAINS=$NETBIRD_RELAY_DOMAIN
- NB_LETSENCRYPT_EMAIL=$NETBIRD_LETSENCRYPT_EMAIL
- NB_LETSENCRYPT_DATA_DIR=/var/lib/netbird-relay
# todo: change to a secure secret
- NB_AUTH_SECRET=$NETBIRD_RELAY_AUTH_SECRET
volumes:
- $RELAY_LE_VOLUMENAME:/var/lib/netbird-relay
ports:
# Direct UDP exposure for QUIC + WebTransport (bypasses Traefik).
- 443:443/udp
labels:
# The TCP WS path stays behind Traefik so the existing /relay route keeps
# working for clients that can't open UDP/443.
- traefik.enable=true
- traefik.http.routers.netbird-relay.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/relay`)
- traefik.http.services.netbird-relay.loadbalancer.server.port=443
# Management
management:
<<: *default
image: netbirdio/management:$NETBIRD_MANAGEMENT_TAG
depends_on:
- dashboard
volumes:
- $MGMT_VOLUMENAME:/var/lib/netbird
- $LETSENCRYPT_VOLUMENAME:/etc/letsencrypt:ro
- ./management.json:/etc/netbird/management.json
command: [
"--port", "33073",
"--log-file", "console",
"--log-level", "info",
"--disable-anonymous-metrics=$NETBIRD_DISABLE_ANONYMOUS_METRICS",
"--single-account-mode-domain=$NETBIRD_MGMT_SINGLE_ACCOUNT_MODE_DOMAIN",
"--dns-domain=$NETBIRD_MGMT_DNS_DOMAIN"
]
labels:
- traefik.enable=true
- traefik.http.routers.netbird-api.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/api`)
- traefik.http.routers.netbird-api.service=netbird-api
- traefik.http.services.netbird-api.loadbalancer.server.port=33073
- traefik.http.routers.netbird-wsproxy-mgmt.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/ws-proxy/management`)
- traefik.http.routers.netbird-wsproxy-mgmt.service=netbird-wsproxy-mgmt
- traefik.http.services.netbird-wsproxy-mgmt.loadbalancer.server.port=33073
- traefik.http.routers.netbird-management.rule=Host(`$NETBIRD_DOMAIN`) && PathPrefix(`/management.ManagementService/`)
- traefik.http.routers.netbird-management.service=netbird-management
- traefik.http.services.netbird-management.loadbalancer.server.port=33073
- traefik.http.services.netbird-management.loadbalancer.server.scheme=h2c
environment:
- NETBIRD_STORE_ENGINE_POSTGRES_DSN=$NETBIRD_STORE_ENGINE_POSTGRES_DSN
- NETBIRD_STORE_ENGINE_MYSQL_DSN=$NETBIRD_STORE_ENGINE_MYSQL_DSN
# Coturn
coturn:
<<: *default
image: coturn/coturn:$COTURN_TAG
domainname: $TURN_DOMAIN
volumes:
- ./turnserver.conf:/etc/turnserver.conf:ro
network_mode: host
command:
- -c /etc/turnserver.conf
volumes:
$MGMT_VOLUMENAME:
$SIGNAL_VOLUMENAME:
$LETSENCRYPT_VOLUMENAME:
$RELAY_LE_VOLUMENAME: