Skip to main content

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-peer in the netbird namespace, two replicas with a RollingUpdate that keeps one connected at all times (maxUnavailable: 0, maxSurge: 1), 20 s termination grace
  • Image: upstream netbirdio/netbird in its -rootless variant, digest-pinned
  • State: /var/lib/netbird is a memory-backed emptyDir, 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 status every 5 s and passes only when both Management and Signal report Connected, 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: true as uid 65532, allowPrivilegeEscalation: false, privileged: false, all capabilities dropped, no service account token; WireGuard runs in userspace, so no /dev/net/tun and no NET_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-encrypted netbird-client Secret and injected via envFrom; see operations/sops for the key handling
  • The netbird namespace 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=netbird and HOME=/var/lib/netbird in the base Deployment point the rootless agent at its writable volume
  • The edge patch adds only the envFrom for the setup-key Secret; there is no NB_* 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​

Kubernetes Metadata
  • 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