NetBird VPN

NetBird v0.66 combined server — WireGuard mesh for remote cluster access, with Authentik SSO and automated IDP token provisioning.

Overview

NetBird provides a WireGuard-based overlay mesh for secure remote access to the homelab. The combined server (netbirdio/netbird-server:0.66.0) runs on Bifrost (Hetzner VPS) and consolidates management, signal, relay, and STUN into a single container.

A routing peer pod runs inside Kubernetes and advertises the cluster subnet 192.168.1.0/24 into the mesh — making all cluster services reachable from any connected NetBird client.

Your laptop (NetBird client)
    │  WireGuard encrypted tunnel
    ▼  netbird.madhan.app:443
Bifrost VPS
    ├─ Traefik  →  netbird-server:80  (management + signal + relay)
    └─ WireGuard mesh ──────────────────────────────────┐
                                          K8s: netbird-peer pod
                                               routes 192.168.1.0/24
                                          Cluster services  192.168.1.220–230

Architecture Diagram

 flowchart LR
    subgraph CLIENTS["NetBird Clients"]
        LAP["Laptop / Phone<br/>NetBird client app"]
    end

    subgraph VPS["Bifrost VPS"]
        TR["Traefik :443"]
        NBS["netbird-server<br/>management · signal · relay"]
        NBD["netbird-dashboard<br/>web UI"]
        NBP["netbird-proxy<br/>*.proxy.madhan.app"]
        NBP2["netbird-agent<br/>WireGuard peer"]
    end

    subgraph K8S["Kubernetes Cluster"]
        NBPEER["netbird-peer pod<br/>network: host<br/>routes 192.168.1.0/24"]
        GW["Cilium Gateway<br/>192.168.1.220"]
        PODS["Service Pods"]
    end

    LAP -->|"WireGuard tunnel<br/>netbird.madhan.app:443"| TR
    TR -->|"gRPC paths"| NBS
    TR -->|"dashboard paths"| NBD
    NBS <-->|"mesh coordination"| NBPEER
    NBS <-->|"mesh coordination"| NBP2
    NBPEER --> GW
    GW --> PODS
    LAP -->|"192.168.1.x via mesh"| NBPEER

Authentik Integration

NetBird uses Authentik as its OIDC identity provider. Users log in to the NetBird dashboard via GitHub (proxied through Authentik). The NetBird server also calls the Authentik management API to sync user groups.

How NB_IDP_MGMT_TOKEN is provisioned

This token is the Authentik API key used by NetBird for user sync. It cannot exist until Authentik is running, creating a chicken-and-egg dependency. The bootstrap.sh script resolves this automatically:

  1. Waits for authentik-server to report healthy
  2. Runs docker exec authentik-server ak shell — a Python script in Authentik's Django context
  3. Calls Token.objects.get_or_create(identifier='netbird-mgmt-token', key=AUTHENTIK_BOOTSTRAP_TOKEN)
  4. Appends NB_IDP_MGMT_TOKEN=<key> to /etc/bifrost/.secrets.env
  5. Starts netbird-server with the token now available

No manual steps required. On subsequent pulumi up runs, if the token is already in .secrets.env, provisioning is skipped.


Server Configuration

core/cloud/bifrost/netbird/config.yaml is a templatebootstrap.sh substitutes ${VAR} placeholders before starting netbird-server (NetBird v0.66 does not expand env vars in its config file natively).

Config fieldTemplate valueSubstituted from
server.authSecret${NB_RELAY_SECRET}.secrets.env
store.encryptionKey${NB_DATA_STORE_KEY}.secrets.env
idp.authentik.managementToken${NB_IDP_MGMT_TOKEN}.secrets.env (auto-provisioned)
server.exposedAddresshttps://netbird.madhan.app:443static
auth.issuerhttps://auth.madhan.app/application/o/netbird/static
reverseProxy.trustedHTTPProxies172.30.0.10/32static (Traefik IP in bifrost_net)
store.enginesqlitestatic

Required Secrets

All secrets come from secrets/bootstrap.sops.yaml via Pulumi's generateBifrostSecretsEnv():

VariablePurposeGenerate withRotate?
NB_DATA_STORE_KEYSQLite encryption keyopenssl rand -base64 32No — DB encrypted with it
NB_RELAY_SECRETRelay auth shared secretopenssl rand -base64 32Yes (all peers reconnect)
NB_IDP_MGMT_TOKENAuthentik API token (user sync)Auto-provisioned by bootstrap.shYes
NB_PROXY_TOKENPersonal access token for netbird-proxyNetBird UI → Settings → Access TokensYes
NB_BIFROST_SETUP_KEYSetup key for netbird-agent on BifrostNetBird UI → Setup KeysYes

Traefik Routing

NetBird traffic on netbird.madhan.app is split across three Traefik routers (defined in core/cloud/bifrost/traefik/dynamic/services.yml):

RouterRuleBackendProtocol
netbird-grpc/signalexchange*/, /management*/ (gRPC)netbird-server:80HTTP/2 cleartext (h2c)
netbird-backend/relay, /api, /oauth2, /ws-proxy/netbird-server:80HTTP
netbird-dashboardall other netbird.madhan.app pathsnetbird-dashboard:80HTTP

STUN (UDP/3478) bypasses Traefik entirely — port-forwarded directly to the host.


K8s Routing Peer

The netbird-peer Deployment in the netbird namespace connects to the WireGuard mesh and advertises 192.168.1.0/24 as a route. This makes all cluster services reachable from any NetBird-connected device.

SettingValue
Imagenetbirdio/netbird:latest
Namespacenetbird (Pod Security Admission: privileged)
Setup keyFrom Infisical /netbirdNETBIRD_SETUP_KEY
Management URLhttps://netbird.madhan.app
CapabilitiesNET_ADMIN, SYS_MODULE
hostNetworktrue — required for kernel WireGuard interface

The setup key is stored in Infisical (path /netbird, key NETBIRD_SETUP_KEY) and synced to the netbird namespace by an InfisicalSecret CR. See Secrets Flow for the InfisicalSecret pattern.


NetBird Dashboard Environment

core/cloud/bifrost/netbird/dashboard.env configures the dashboard container:

VariableValue
NETBIRD_MGMT_API_ENDPOINThttps://netbird.madhan.app
AUTH_AUTHORITYhttps://auth.madhan.app/application/o/netbird/
AUTH_CLIENT_IDAuthentik OAuth2 client ID for NetBird app
USE_AUTH0false
AUTH_SUPPORTED_SCOPESopenid profile email groups

NetBird Reverse Proxy (*.proxy.madhan.app)

The netbird-proxy container exposes NetBird peers as TCP endpoints via *.proxy.madhan.app. Traefik routes *.proxy.madhan.app TCP traffic to netbird-proxy:8443 via a wildcard TCP router.

core/cloud/bifrost/netbird/proxy.env:

NB_PROXY_MANAGEMENT_ADDRESS=netbird-server:80
NB_PROXY_DOMAIN=proxy.madhan.app
NB_PROXY_ACME_CERTIFICATES=true

NB_PROXY_TOKEN is injected from .secrets.env (not in proxy.env — Docker Compose env_file does not expand variables).

The proxy container is only started by bootstrap.sh if NB_PROXY_TOKEN is present in .secrets.env. If it's missing, the bootstrap prints setup instructions and skips it.


First-Time Setup Checklist

After the initial just core hetzner up succeeds and all containers are running:

  • Open https://netbird.madhan.app → Log in with GitHub (via Authentik)
  • Setup Keys → Add key bifrost-agent (Reusable) for the VPS WireGuard agent
  • Setup Keys → Add key k8s-routing-peer (Reusable) for the K8s pod
  • Settings → Access Tokens → Create a Personal Access Token — copy it
  • sops edit secrets/bootstrap.sops.yaml:
    • Add NB_PROXY_TOKEN: <personal access token>
    • Add NB_BIFROST_SETUP_KEY: <bifrost-agent key>
  • just core hetzner up — bootstrap.sh starts netbird-proxy and netbird-agent with the new tokens
  • Open Infisical → Project homelab-prod → Env prod → Path /netbird:
    • Add NETBIRD_SETUP_KEY: <k8s-routing-peer key>
  • Network Routes → Add Route: network 192.168.1.0/24, peer k8s-routing-peer
  • Verify peers are connected in the NetBird dashboard