Accessing the Cluster Remotely with Netbird #
To access the cluster from outside of the home network we can deploy the Netbird operator for Kubernetes. Similar to tailscale, this allows us to access all of our services through a private mesh network. The operator handles the creation of network router nodes to create secure Wireguard tunnels to our exposed services.
Prerequisites #
Using the Netbird dashboard, a DNS zone, named “homelab” in this case, must be creted along with a srevice user api key with admin permissions.
Deploying Netbird #
Helm #
We can use the official Helm chart to install the Netbird Operator:
{
self,
inputs,
...
}:
{
flake.modules.nixos.netbird-operator-charts =
{
config,
lib,
pkgs,
...
}:
let
netbirdOperatorChart = {
name = "netbird-operator";
repo = "oci://ghcr.io/netbirdio/helm-charts/netbird-operator";
version = "0.4.1";
hash = "sha256-gRdZViio1QZYNlaEEKk/36F0wc3MnMgtTZE2HTIx4qo=";
};
in
{
config = lib.mkIf config.netbird-operator.enable {
services.k3s.autoDeployCharts = {
netbird-operator = netbirdOperatorChart // {
targetNamespace = "netbird";
createNamespace = true;
};
};
};
};
}Preloading the Operator and Router Images #
The images can be preloaded as usual with their corresponding helm values set:
{
self,
inputs,
...
}:
{
flake.modules.nixos.netbird-operator-images =
{
config,
lib,
pkgs,
...
}:
let
operatorImage = pkgs.dockerTools.pullImage {
imageName = "ghcr.io/netbirdio/netbird-operator";
imageDigest = "sha256:0f89a7385eadfde8a47adbfa0ee7913e8d0b938293e08615ca0aa1deab91fcb4";
hash = "sha256-DYq4Gb7aGoH0L915fCf9f+gP17CZHfjbq8kR21/kuc4=";
finalImageTag = "v0.4.1";
arch = "amd64";
};
routerImage = pkgs.dockerTools.pullImage {
imageName = "ghcr.io/netbirdio/netbird";
imageDigest = "sha256:4c37fb63f33531fc0c5eec272839b0e4d290c595a0b15bf5fd9a288df1890392";
hash = "sha256-TDBO4bjEhbcj01z1ZdoSjfPS226LmC3bxOyyusFwh2M=";
finalImageTag = "0.71.0-rootless";
arch = "amd64";
};
in
{
config = lib.mkIf config.netbird-operator.enable {
services.k3s = {
images = [
operatorImage
routerImage
];
autoDeployCharts.netbird-operator.values = {
operator.image = {
repository = operatorImage.imageName;
tag = operatorImage.imageTag;
};
routingClientImage = "${routerImage.imageName}:${routerImage.imageTag}";
};
};
};
};
}Configuring the Network Router #
Using the netbird.io api we can deploy our network routers:
{
self,
inputs,
...
}:
{
flake.modules.nixos.netbird-operator-router =
{
config,
lib,
pkgs,
...
}:
{
config = lib.mkIf config.netbird-operator.enable {
services.k3s.autoDeployCharts.netbird-operator.extraDeploy = [
{
apiVersion = "netbird.io/v1alpha1";
kind = "NetworkRouter";
metadata = {
name = "homelab";
namespace = "netbird";
};
spec = {
dnsZoneRef.name = "homelab";
workloadOverride = {
replicas = 1;
podTemplate.spec = {
dnsConfig.options = [
{
name = "ndots";
value = "0";
}
];
resources = {
requests.cpu = "100m";
requests.memory = "128Mi";
limits.cpu = "250m";
limits.memory = "256Mi";
};
};
};
};
}
];
};
};
}Adding the Netbird Secret #
Using sops-nix we can add a separate manifest to deploy the required Kubernetes secrets:
{
self,
inputs,
...
}:
{
flake.modules.nixos.netbird-operator-secrets =
{
config,
lib,
pkgs,
...
}:
{
config =
lib.mkIf
(config.netbird-operator.enable && config.secrets.enable && config.secrets.netbird-operator.enable)
{
sops = {
secrets = {
"netbird/key" = { };
};
templates = {
netbirdMgmtApiKey = {
content = builtins.toJSON {
apiVersion = "v1";
kind = "Secret";
metadata = {
name = "netbird-mgmt-api-key";
namespace = "netbird";
};
type = "Opaque";
immutable = true;
stringData = {
NB_API_KEY = config.sops.placeholder."netbird/key";
};
};
path = "/var/lib/rancher/k3s/server/manifests/netbird-mgmt-api-key.json";
};
};
};
};
};
}Exposing Services #
Once installed, we can deploy NetworkResource manifests to expose Kubernetes services:
{
self,
inputs,
...
}:
{
flake.modules.nixos.immich-services =
{
config,
lib,
pkgs,
...
}:
{
config = lib.mkIf config.immich.enable {
services.k3s.autoDeployCharts.immich = {
values.service.main = {
type = "LoadBalancer";
annotations = {
"metallb.io/address-pool" = "default";
"metallb.io/allow-shared-ip" = "immich";
"metallb.io/loadBalancerIPs" = "192.168.1.205";
};
};
extraDeploy = [
{
apiVersion = "v1";
kind = "Service";
metadata = {
name = "immich";
namespace = "immich";
};
spec = {
type = "ClusterIP";
selector = {
"app.kubernetes.io/controller" = "main";
"app.kubernetes.io/instance" = "immich";
"app.kubernetes.io/name" = "server";
};
ports = [
{
name = "http";
port = 80;
targetPort = 2283;
protocol = "TCP";
}
];
};
}
{
apiVersion = "netbird.io/v1alpha1";
kind = "NetworkResource";
metadata = {
name = "immich";
namespace = "immich";
};
spec = {
networkRouterRef = {
name = "homelab";
namespace = "netbird";
};
serviceRef = {
name = "immich";
namespace = "immich";
};
groups = [ { name = "All"; } ];
};
}
];
};
};
};
}Managing the Cluster Network in Netbird #
With our services deployed, we can manage the generate resources in the “homelab” network on the Netbird dashboard: