While NetBird doesn’t provide official Helm charts yet, you can deploy it on Kubernetes using custom manifests based on the Docker Compose configuration.
NetBird is primarily designed for Docker Compose deployments. Kubernetes support is community-driven. For production Kubernetes deployments, consider using NetBird Cloud or contributing Helm charts to the project.
Prerequisites
Kubernetes cluster (v1.20+)
kubectl configured with cluster access
Ingress controller (nginx, Traefik, or similar)
Cert-manager for automatic TLS (recommended)
Storage class for persistent volumes
Architecture on Kubernetes
A Kubernetes deployment of NetBird consists of:
NetBird Server Deployment - Combined server (management, signal, relay, STUN)
Dashboard Deployment - Web UI
ConfigMaps - For config.yaml and dashboard environment
Secrets - For encryption keys and OAuth secrets
Services - ClusterIP for internal communication, LoadBalancer/NodePort for STUN
Ingress - HTTPS routing to dashboard and server
PersistentVolumeClaim - For SQLite database storage
Namespace and ConfigMap
Create Namespace
apiVersion : v1
kind : Namespace
metadata :
name : netbird
ConfigMap for Server Configuration
Create a ConfigMap with the combined server configuration:
apiVersion : v1
kind : ConfigMap
metadata :
name : netbird-server-config
namespace : netbird
data :
config.yaml : |
server:
listenAddress: ":80"
exposedAddress: "https://netbird.example.com:443"
stunPorts:
- 3478
metricsPort: 9090
healthcheckAddress: ":9000"
logLevel: "info"
logFile: "console"
authSecret: "your-relay-auth-secret" # Generate with: openssl rand -base64 32
dataDir: "/var/lib/netbird"
auth:
issuer: "https://netbird.example.com/oauth2"
signKeyRefreshEnabled: true
dashboardRedirectURIs:
- "https://netbird.example.com/nb-auth"
- "https://netbird.example.com/nb-silent-auth"
cliRedirectURIs:
- "http://localhost:53000/"
reverseProxy:
trustedHTTPProxies:
- "10.0.0.0/8" # Adjust for your cluster network
store:
engine: "sqlite"
encryptionKey: "" # Will be set from Secret
Replace netbird.example.com with your actual domain. Generate secure secrets using openssl rand -base64 32.
ConfigMap for Dashboard
apiVersion : v1
kind : ConfigMap
metadata :
name : netbird-dashboard-config
namespace : netbird
data :
NETBIRD_MGMT_API_ENDPOINT : "https://netbird.example.com"
NETBIRD_MGMT_GRPC_API_ENDPOINT : "https://netbird.example.com"
AUTH_AUDIENCE : "netbird-dashboard"
AUTH_CLIENT_ID : "netbird-dashboard"
AUTH_AUTHORITY : "https://netbird.example.com/oauth2"
USE_AUTH0 : "false"
AUTH_SUPPORTED_SCOPES : "openid profile email groups"
AUTH_REDIRECT_URI : "/nb-auth"
AUTH_SILENT_REDIRECT_URI : "/nb-silent-auth"
NGINX_SSL_PORT : "443"
LETSENCRYPT_DOMAIN : "none"
Secrets
Create secrets for sensitive data:
apiVersion : v1
kind : Secret
metadata :
name : netbird-secrets
namespace : netbird
type : Opaque
stringData :
datastore-encryption-key : "your-base64-encryption-key" # openssl rand -base64 32
relay-auth-secret : "your-relay-secret" # openssl rand -base64 32 | sed 's/=//g'
Store these secrets securely in your secret management system (e.g., Sealed Secrets, External Secrets Operator, or HashiCorp Vault).
Persistent Storage
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : netbird-data
namespace : netbird
spec :
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 5Gi
storageClassName : standard # Adjust for your cluster
NetBird Server Deployment
apiVersion : apps/v1
kind : Deployment
metadata :
name : netbird-server
namespace : netbird
spec :
replicas : 1 # Do not scale beyond 1 with SQLite
selector :
matchLabels :
app : netbird-server
template :
metadata :
labels :
app : netbird-server
spec :
containers :
- name : netbird-server
image : netbirdio/netbird-server:latest
ports :
- containerPort : 80
name : http
protocol : TCP
- containerPort : 3478
name : stun
protocol : UDP
- containerPort : 9090
name : metrics
protocol : TCP
volumeMounts :
- name : config
mountPath : /etc/netbird/config.yaml
subPath : config.yaml
- name : data
mountPath : /var/lib/netbird
env :
- name : NETBIRD_STORE_ENCRYPTION_KEY
valueFrom :
secretKeyRef :
name : netbird-secrets
key : datastore-encryption-key
resources :
requests :
memory : "512Mi"
cpu : "250m"
limits :
memory : "2Gi"
cpu : "1000m"
livenessProbe :
httpGet :
path : /
port : 9000
initialDelaySeconds : 30
periodSeconds : 10
readinessProbe :
httpGet :
path : /
port : 9000
initialDelaySeconds : 10
periodSeconds : 5
volumes :
- name : config
configMap :
name : netbird-server-config
- name : data
persistentVolumeClaim :
claimName : netbird-data
With SQLite, you cannot run multiple replicas. For high availability, configure PostgreSQL or MySQL as the database backend and set replicas: 2+.
Dashboard Deployment
apiVersion : apps/v1
kind : Deployment
metadata :
name : netbird-dashboard
namespace : netbird
spec :
replicas : 2 # Can scale horizontally
selector :
matchLabels :
app : netbird-dashboard
template :
metadata :
labels :
app : netbird-dashboard
spec :
containers :
- name : dashboard
image : netbirdio/dashboard:latest
ports :
- containerPort : 80
name : http
protocol : TCP
envFrom :
- configMapRef :
name : netbird-dashboard-config
resources :
requests :
memory : "128Mi"
cpu : "100m"
limits :
memory : "512Mi"
cpu : "500m"
livenessProbe :
httpGet :
path : /
port : 80
initialDelaySeconds : 10
periodSeconds : 10
readinessProbe :
httpGet :
path : /
port : 80
initialDelaySeconds : 5
periodSeconds : 5
Services
NetBird Server Service
apiVersion : v1
kind : Service
metadata :
name : netbird-server
namespace : netbird
spec :
selector :
app : netbird-server
ports :
- name : http
port : 80
targetPort : 80
protocol : TCP
- name : metrics
port : 9090
targetPort : 9090
protocol : TCP
type : ClusterIP
---
apiVersion : v1
kind : Service
metadata :
name : netbird-stun
namespace : netbird
spec :
selector :
app : netbird-server
ports :
- name : stun
port : 3478
targetPort : 3478
protocol : UDP
type : LoadBalancer # Or NodePort, depending on your cluster
Dashboard Service
apiVersion : v1
kind : Service
metadata :
name : netbird-dashboard
namespace : netbird
spec :
selector :
app : netbird-dashboard
ports :
- name : http
port : 80
targetPort : 80
protocol : TCP
type : ClusterIP
Ingress Configuration
With Nginx Ingress Controller
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : netbird
namespace : netbird
annotations :
cert-manager.io/cluster-issuer : "letsencrypt-prod"
nginx.ingress.kubernetes.io/backend-protocol : "HTTP"
nginx.ingress.kubernetes.io/proxy-read-timeout : "86400" # 1 day for gRPC
nginx.ingress.kubernetes.io/proxy-send-timeout : "86400"
nginx.ingress.kubernetes.io/client-header-timeout : "86400"
nginx.ingress.kubernetes.io/proxy-body-size : "0"
# Enable HTTP/2 for gRPC
nginx.ingress.kubernetes.io/http2-push-preload : "true"
spec :
ingressClassName : nginx
tls :
- hosts :
- netbird.example.com
secretName : netbird-tls
rules :
- host : netbird.example.com
http :
paths :
# gRPC paths (high priority)
- path : /signalexchange.SignalExchange
pathType : Prefix
backend :
service :
name : netbird-server
port :
number : 80
- path : /management.ManagementService
pathType : Prefix
backend :
service :
name : netbird-server
port :
number : 80
# Backend paths (relay, API, OAuth)
- path : /relay
pathType : Prefix
backend :
service :
name : netbird-server
port :
number : 80
- path : /ws-proxy
pathType : Prefix
backend :
service :
name : netbird-server
port :
number : 80
- path : /api
pathType : Prefix
backend :
service :
name : netbird-server
port :
number : 80
- path : /oauth2
pathType : Prefix
backend :
service :
name : netbird-server
port :
number : 80
# Dashboard (default)
- path : /
pathType : Prefix
backend :
service :
name : netbird-dashboard
port :
number : 80
For gRPC to work properly, ensure your ingress controller supports HTTP/2 and has appropriate timeout settings.
Cert-Manager ClusterIssuer
For automatic TLS certificates:
apiVersion : cert-manager.io/v1
kind : ClusterIssuer
metadata :
name : letsencrypt-prod
spec :
acme :
server : https://acme-v02.api.letsencrypt.org/directory
email : [email protected]
privateKeySecretRef :
name : letsencrypt-prod
solvers :
- http01 :
ingress :
class : nginx
Deployment
Apply all manifests:
# Create namespace
kubectl apply -f namespace.yaml
# Create secrets (do this first)
kubectl apply -f secrets.yaml
# Create ConfigMaps
kubectl apply -f configmap-server.yaml
kubectl apply -f configmap-dashboard.yaml
# Create PVC
kubectl apply -f pvc.yaml
# Deploy services
kubectl apply -f deployment-server.yaml
kubectl apply -f deployment-dashboard.yaml
# Create services
kubectl apply -f service-server.yaml
kubectl apply -f service-dashboard.yaml
# Create ingress (requires cert-manager)
kubectl apply -f clusterissuer.yaml
kubectl apply -f ingress.yaml
Verification
# Check pod status
kubectl get pods -n netbird
# Check logs
kubectl logs -n netbird -l app=netbird-server -f
# Check ingress
kubectl get ingress -n netbird
# Test connectivity
curl https://netbird.example.com/api/health
Scaling Considerations
Using SQLite (Default)
Cannot scale beyond 1 replica
Suitable for small deployments (less than 100 peers)
Single point of failure
Using PostgreSQL/MySQL
For production Kubernetes deployments:
Deploy PostgreSQL or MySQL in the cluster
Update config.yaml:
store :
engine : "postgres"
encryptionKey : "<from-secret>"
Add environment variable to deployment:
env :
- name : NETBIRD_STORE_ENGINE_POSTGRES_DSN
valueFrom :
secretKeyRef :
name : netbird-secrets
key : postgres-dsn
Scale to multiple replicas:
kubectl scale deployment netbird-server -n netbird --replicas=3
Monitoring
Prometheus Integration
NetBird exposes metrics on port 9090:
apiVersion : v1
kind : Service
metadata :
name : netbird-server-metrics
namespace : netbird
labels :
app : netbird-server
spec :
selector :
app : netbird-server
ports :
- name : metrics
port : 9090
targetPort : 9090
type : ClusterIP
Configure ServiceMonitor for Prometheus Operator:
apiVersion : monitoring.coreos.com/v1
kind : ServiceMonitor
metadata :
name : netbird-server
namespace : netbird
spec :
selector :
matchLabels :
app : netbird-server
endpoints :
- port : metrics
interval : 30s
Troubleshooting
Pods Not Starting
# Check pod events
kubectl describe pod -n netbird < pod-nam e >
# Check logs
kubectl logs -n netbird < pod-nam e > --previous
Ingress Issues
# Check ingress configuration
kubectl describe ingress -n netbird netbird
# Check certificate status
kubectl get certificate -n netbird
kubectl describe certificate -n netbird netbird-tls
gRPC Connection Failures
Ensure ingress controller supports:
HTTP/2
gRPC protocol
Extended timeouts for long-lived connections
STUN Port Not Accessible
# Check LoadBalancer external IP
kubectl get svc -n netbird netbird-stun
# Verify UDP port
netcat -u < external-i p > 3478
If you develop Helm charts or improved Kubernetes manifests, consider contributing them to the NetBird project!
Next Steps
Docker Compose Simpler alternative for non-Kubernetes environments
NetBird Cloud Managed NetBird without infrastructure management
Access Control Configure network policies after deployment
Monitoring Set up observability for your deployment