NetBird client (edge)
The edge cluster's own seat on the mesh.
This page covers the NetBird agent that runs as a workload on the edge cluster — the single public Hetzner node that terminates the homelab's internet-facing listeners. It is one of several NetBird actors in the lab, and the others have their own pages: the mesh itself, its networks, groups and policies, is described on Fabric → NetBird; the cross-layer story of how peers get their keys is on NetBird connectivity; the Docker agent on the Maresa Synology is NetBird agent (Maresa); and the Envoy sidecar that actually carries edge-to-production traffic is documented with the PROXY-protocol-v2 chain. This deployment is none of those — it is the standalone client peer registered in the edge network.
The Deployment is called netbird-routing-peer, but its pod is deliberately not equipped to route: it runs the rootless image with every capability dropped, which the manifest itself notes as sufficient because the client is not used as a routing peer. What the pod does is hold a userspace WireGuard session to the NetBird management plane using a setup key issued by the edge Tofu environment, so the cluster is represented on the mesh even when nothing else on the node is. Which policies that peer participates in follows from the groups its setup key assigns, declared in tofu/environment/edge/netbird.tf.
Installation
Architecture
- Deployment:
netbird-routing-peerin thenetbirdnamespace, two replicas with aRollingUpdatethat keeps one connected at all times (maxUnavailable: 0,maxSurge: 1), 20 s termination grace - Image: upstream
netbirdio/netbirdin its-rootlessvariant, digest-pinned - State:
/var/lib/netbirdis a memory-backedemptyDir, so each pod enrols as a fresh peer and leaves nothing behind; that matches the ephemeral setup keys the edge environment issues, which let NetBird garbage-collect peers that disappear - Readiness: an exec probe runs
netbird statusevery 5 s and passes only when both Management and Signal reportConnected, so a replica that lost its control-plane session drops out of the rollout gate - Delivery: the edge overlay ships as the cosign-verified OCI artifact
oci-edge-app-netbird-client, reconciled by Flux with SOPS decryption — the same path as every other edge app (GitOps flow)
Security
runAsNonRoot: trueas uid65532,allowPrivilegeEscalation: false,privileged: false, all capabilities dropped, no service account token; WireGuard runs in userspace, so no/dev/net/tunand noNET_ADMIN— the opposite of the edge Envoy sidecar, which needs both- The setup key (
NB_SETUP_KEY) is the only secret, kept in the SOPS-encryptednetbird-clientSecret and injected viaenvFrom; see operations/sops for the key handling - The
netbirdnamespace does not pull in the app-network-policy component, so no NetworkPolicy constrains the pods; the agent needs outbound reach to the NetBird management, signal, relay and STUN endpoints
Updates
Managed by Renovate; the image is digest-pinned. Rolling one replica at a time means a version bump never takes the cluster off the mesh.
Configuration Management
USER=netbirdandHOME=/var/lib/netbirdin the base Deployment point the rootless agent at its writable volume- The edge patch adds only the
envFromfor the setup-key Secret; there is noNB_*tuning beyond that, and the peer's group membership and policies live in OpenTofu, not in Kubernetes
Usage
Nothing to operate day to day. kubectl -n netbird get pods shows two ready pods when the mesh session is healthy; netbird status inside a pod prints the management, signal and peer state. If both replicas go unready at once the setup key has most likely been revoked or rotated in Tofu, and the Secret needs the new value.
Cluster-specific deviations from the above live next to the manifests — see k8s/apps/netbird-client/edge; there is no per-cluster README yet.
Cluster Deployment
- Image:
netbirdio/netbird:0.81.2-rootless@sha256:0ff1cbfc435d0fe39ee8824fee9a6ec2f56eb472b5d45abafd53ecee90161d60
Rendered manifests (kustomize build)
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kustomize.toolkit.fluxcd.io/force: enabled
name: netbird-routing-peer
namespace: netbird
spec:
replicas: 2
revisionHistoryLimit: 2
selector:
matchLabels:
app.kubernetes.io/component: routing-peer
app.kubernetes.io/name: netbird
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
labels:
app.kubernetes.io/component: routing-peer
app.kubernetes.io/name: netbird
spec:
automountServiceAccountToken: false
containers:
- env:
- name: USER
value: netbird
- name: HOME
value: /var/lib/netbird
envFrom:
- secretRef:
name: netbird-client
image: netbirdio/netbird:0.81.2-rootless@sha256:0ff1cbfc435d0fe39ee8824fee9a6ec2f56eb472b5d45abafd53ecee90161d60
name: netbird
readinessProbe:
exec:
command:
- sh
- -ec
- |
out="$(netbird status 2>/dev/null || true)"
echo "$out" | grep -Eq '^Management:\s+Connected' &&
echo "$out" | grep -Eq '^Signal:\s+Connected'
failureThreshold: 3
periodSeconds: 5
timeoutSeconds: 2
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
runAsNonRoot: true
runAsUser: 65532
volumeMounts:
- mountPath: /var/lib/netbird
name: netbird-data
terminationGracePeriodSeconds: 20
volumes:
- emptyDir:
medium: Memory
name: netbird-data