Getting Started
This guide walks you through setting up nixlab from scratch: generating keys, filling in vars.nix, preparing your first node, and deploying the cluster.
Prerequisites
You need the following tools on your workstation (not on the nodes):
- Nix with flakes enabled (
experimental-features = nix-command flakesin~/.config/nix/nix.conf) - age: for generating the encryption key
- sops: for creating and editing the secrets file
- Colmena: for deploying to nodes
Install them all with:
nix profile install nixpkgs#age nixpkgs#sops nixpkgs#colmena
Or temporarily via nix shell:
nix shell nixpkgs#age nixpkgs#sops nixpkgs#colmena
Step 1: Clone the repo
git clone https://github.com/thatbagu/nixlab
cd nixlab
Step 2: Generate your age key
nixlab uses age for encrypting secrets. Generate a key pair:
age-keygen -o ~/.config/sops/age/keys.txt
This prints the public key to stdout. Copy it - you need it in the next step.
Now update .sops.yaml with your public key. Open it and replace the placeholder:
keys:
- &primary age1REPLACE_WITH_YOUR_AGE_PUBLIC_KEY
Change age1REPLACE_WITH_YOUR_AGE_PUBLIC_KEY to the public key that age-keygen printed.
Step 3: Generate a cluster SSH key
All nodes use a single SSH key for cluster access:
ssh-keygen -t ed25519 -C "nixlab-cluster" -f ~/.ssh/nixlab-cluster
Note the public key:
cat ~/.ssh/nixlab-cluster.pub
Step 4: Fill in vars.nix
Open vars.nix and replace every placeholder value with your real values:
{
username = "youruser"; # your Linux username
timezone = "Europe/Berlin"; # timedatectl list-timezones
clusterSshKey = "ssh-ed25519 AAAA... youruser@host"; # from step 3
nodes = {
master = {
hostname = "mymaster"; # must match hosts/<hostname>/
master = true;
disk = "/dev/sda"; # check with lsblk on the target machine
tags = [ "homelab" "master" "mymaster" ];
};
};
domain = "yourdomain.example.com"; # Cloudflare-managed domain
metallbPool = "192.168.1.192/26"; # outside your DHCP range
piholeIp = "192.168.1.250";
wireguardIp = "192.168.1.194";
nginxIp = "192.168.1.193";
upstreamDns = "192.168.1.1"; # your router
wireguardUsers = {}; # add users later with add-wg-user.sh
}
The IPs in metallbPool, piholeIp, wireguardIp, and nginxIp must all be in the same subnet and outside your router's DHCP assignment range.
See Configuration for a full field reference.
Step 5: Create and encrypt secrets.yaml
Copy the example file:
cp modules/system/sops/secrets.yaml.example modules/system/sops/secrets.yaml
Fill in the real values. For secrets you need to generate:
# k3s cluster join token - any long random string
openssl rand -hex 32
# WireGuard server keys
wg genkey | tee /tmp/wg-server.key | wg pubkey > /tmp/wg-server.pub
cat /tmp/wg-server.key # wireguard_server_private_key
cat /tmp/wg-server.pub # wireguard_server_public_key
# Linux user password hash (replace 'yourpassword')
mkpasswd -m sha-512 yourpassword
For wireguard_server_endpoint: use your public IP or a DDNS hostname. The DDNS service (if enabled) will keep Cloudflare updated, but the WireGuard endpoint in secrets.yaml is what clients use to connect.
For Cloudflare credentials: create an API token at https://dash.cloudflare.com/profile/api-tokens with Zone:DNS:Edit permission. The email is your Cloudflare account email.
For private_ssh_key: this is the private key of the cluster SSH key from step 3. The full private key, including the header/footer lines.
Once secrets.yaml is filled in with real values, encrypt it:
sops --encrypt --in-place modules/system/sops/secrets.yaml
SOPS will use the age key from .sops.yaml. The encrypted file is safe to commit - commit it now:
git add modules/system/sops/secrets.yaml
git commit -m "add encrypted secrets"
Step 6: Prepare the first node's hardware config
Boot your target machine with a NixOS installer ISO. Once booted:
nixos-generate-config --no-filesystems
cat /etc/nixos/hardware-configuration.nix
The --no-filesystems flag skips filesystem detection (Disko handles that). Copy the output to your workstation:
mkdir -p hosts/mymaster
# paste the hardware-configuration.nix content here
The file should look something like:
{ config, lib, modulesPath, ... }:
{
imports = [ (modulesPath + "/installer/scan/not-detected.nix") ];
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ]; # or kvm-amd
boot.extraModulePackages = [ ];
swapDevices = [ ];
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}
The hostname in vars.nodes must match the directory name under hosts/.
Step 7: Initial install
From the NixOS installer on the target machine, with the repo available (via git clone or a mounted drive):
# Install using Disko to partition the disk, then NixOS
nix run github:nix-community/disko -- --mode disko --flake .#mymaster
nixos-install --flake .#mymaster --no-root-password
Alternatively, if you already have a running NixOS system on the node (even a minimal one), you can deploy directly from your workstation:
colmena apply --on mymaster
Colmena connects via SSH (<hostname>.local using mDNS, as the targetHost in the flake) and switches the system.
Step 8: Verify
After the install reboots:
# SSH into the master
ssh -i ~/.ssh/nixlab-cluster youruser@mymaster.local
# Check k3s is running
sudo k3s kubectl get nodes
# Watch service deployment (takes a few minutes on first boot)
sudo journalctl -fu k8s-deploy
The k8s-deploy service applies all Kubernetes charts in dependency order. Once it finishes, all services should be running:
sudo k3s kubectl get pods --all-namespaces
Step 9: Add more nodes
See Adding Nodes.
Step 10: Add VPN users
See WireGuard VPN.
Subsequent deploys
After changing vars.nix or any module:
# Deploy to all nodes
colmena apply
# Deploy only to master
colmena apply --on @master
# Deploy only to workers
colmena apply --on @worker
# Deploy to a specific node
colmena apply --on mymaster
Colmena uses the tags field in vars.nodes entries to resolve @master and @worker selectors.