Netbox + Caddy + Tailscale + NixOS = ✨

Warum schwer machen, wenn es auch leicht geht?

August 9, 2025

Einmal Netbox zum Mitnehmen bitte

Neulich kam mir der Gedanke, all meine Hardware zu inventarisieren. Raspberry Pi’s, Mini-PCs, Router, Switches und mehr. Eine kurze Recherche im Internet verrät, dass Netbox das beste Tool dafür ist. 👨🏽‍💻 Mein Plan steht also:

  1. In Proxmox einen LXC-Container erstllen, mit NixOS als Gastbetriebssystem.
  2. Tailscale konfigurieren.
  3. Netbox konfigurieren.
  4. Caddy-Webserver konfigurieren.

Proxmox

Ich nutze Proxmox als Hypervisor. Um NixOS in einem LXC-Container zu betreiben, kann man leider keine Standart-".iso"-Datei verwenden 😑, sondern muss eine speziell kompilierte Version nutzen. Das liegt daran, dass NixOS das komplette System definiert. Als Container möchte man allerdings spezielle Einstellungen, wie z. B. DNS-Einstellungen, oder Netzwerk-Einstellungen, über die grafische Oberfläche von Proxmox konfigurieren. Um einen NixOS-Container in Proxmox zu betreiben, folge am Besten der NixOS-Wiki-Anleitung.

Bevor man den Container startet, sollte man noch ein paar Anpassungen vornehmen, wenn man, wie ich, Netbox in sein “Tailnet” mit aufnehmen möchte. 👾 Damit Tailscale funktioniert, ergänze folgende zwei Zeilen in /etc/pve/lxc/<Containernummer>.conf:


lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net/tun dev/net/tun none bind,create=file

Damit erhält Tailscale Zugang zum dev/net/tun-Device und kann damit einen Wireshark-VPN-Tunnel zu anderen Tailnet-Mitgliedern aufbauen. 🔐 Jetzt kann der Container gestartet werden.

NixOS

Nach dem Start muss das Betriebssystem konfiguriert werden. Dazu nutze ich meine Default-NixOS-Konfiguration für einen Proxmox-LXC-Container. ✏️ Passe die entsprechenden Variablen an deine Umgebung an.


{ config, modulesPath, pkgs, lib, ... }:
{
  imports = [ (modulesPath + "/virtualisation/proxmox-lxc.nix") ];
 
  # Nix Settings
  nix = {
        gc = {
                automatic = true;
                dates = "weekly";
                options = "--delete-older-than 30d";
        };
        extraOptions = ''min-free = ${toString (500 * 1024 * 1024)}'';
        settings = {
                sandbox = false;
                auto-optimise-store = true;
                experimental-features = [ "nix-command" "flakes" ];
        };
  };

  # Proxmox settings
  proxmoxLXC = {
        manageNetwork = true;
        privileged = true;
  };

  # Networking
  networking = {
        hostName = ""; # <-- Hostname hier ergänzen!
        firewall = {
                enable = true;
                trustedInterfaces = [ "tailscale0" ];
        };
  };

  # General system settings
  system.copySystemConfiguration = true;
  time.timeZone = "UTC";
  console.keyMap = "de";
  programs.zsh.enable = true;

  # User
  users = {
        users = {
                username = { # <-- Passe den usernamen an.
                        isNormalUser = true;
                        extraGroups = [ "wheel" "networkmanager" ];
                        password = "nixos"; # <-- Wähle ein Passwort.
                        openssh.authorizedKeys.keys = [
                                "" # <-- PUBLIC ssh-Schlüssel hier einfügen. 
                        ];
                        shell = pkgs.zsh;
                };
                root = {
                        hashedPassword = "*";
                };
        };
        mutableUsers = false;
        defaultUserShell = pkgs.zsh;
  };

  # Security
  security = {
        sudo.execWheelOnly = true;
        acme = {
                acceptTerms = true;
                defaults.email = ""; # <-- E-Mail Adresse hier einfügen.
        };
  };

  # SSH
  services.openssh = {
      enable = true;
      settings.PasswordAuthentication = true; # <-- Setze den Wert auf "false, sobald der ssh-Schlüssel ergänzt wurde.
      extraConfig = ''
          AllowTcpForwarding yes
          X11Forwarding no
          AllowAgentForwarding no
          # AuthenticationMethods publickey # <-- Aktiviere diese Zeile,
          # sobald der ssh-Schlüssel ergänzt wurde.
      '';
  };
  
  system.stateVersion = "25.05"; # <-- Passe die initiale Version an; ÄNDERE DEN WERT DANACH NIE WIEDER, AUCH WENN NIXOS UPGRADET!
}

Bevor die Konfiguration angewendet wird, können bereits die Dienste in der NixOS-Konfiguration ergänzt werden.

Tailscale

Zunächst einmal Tailscale. Vier Zeilen, und der Service läuft:


# Tailscale
services.tailscale = {
        enable = true;
        permitCertUid = "caddy";
};

Conclusion

Wenn tailscale das TLS-Zertifikat ausstellt (Domain endet auf “.ts.net”), muss der Webserver, in diesem Fall caddy, auf das Zertifikat zugreifen können. Dazu setzt man permitCertUid auf die User-ID caddy.

Netbox

Netbox Service

Als Nächstes ist Netbox an der Reihe. Die NixOS-Konfiguration besteht auch hier wieder aus vier simplen Zeilen:


# Netbox
services.netbox = {
        enable = true;
        secretKeyFile = "/var/lib/netbox/secret-key-file";
};

Damit Netbox funktioniert, braucht es ein “50 char”-langes Geheimnis (siehe Netbox-Dokumentation). Laut der Dokumentation soll man ein mitgeliefertes Skript nutzen, um den Schlüssel zu erstellen. Dies ist hier allerdings hier nicht möglich, da das Skript ja noch nicht existiert. 😱 Lösung: Ein “50 char”-langes Passwort aus einem Passwort-Manager funktioniert auch. 🤗 Jetzt nur noch den Schlüssel unter /var/lib/netbox/secret-key-file ablegen und fertig.

Postgresql Backup (optional)

Netbox nutzt unter der Haube eine PostgreSQL-Datenbank 🔍. NixOS hat dafür sogar einen Back-up-Service mit an Bord. Dieser Schritt ist zwar optional, allerdings gilt das Motto:

Quote

No Back-up; No pity 💾.

Schaden kann es nicht. 🤷🏽‍♂️


# Postgresql Backup
services.postgresqlBackup = {
        enable = true;
        backupAll = true;
};

Caddy

Last but not least, kommt Caddy zum Zug (no pun intended 🚙🚂). Hier gibt es drei Stolpersteine zu beachten.

Static files

Netbox’ statische Web-Dokumente müssen von einem anderen Pfad gehosted werden. Caddy muss also wissen, dass der Standard-Request auf den internen Netbox-Server umgeleitet werden soll, aber Requests, die auf /static/* abzielen, soll ein Fileserver handhaben.

50x-Error

Sollte der Browser einen 50x-HTML-Error werfen, überprüfe die Loopback-Adresse. In meinem Fall (ich nutze eine “IPv6-only”-Infrastruktur) musste ich [::1]:8001 explizit angeben. Weitere Optionen sind localhost:8001, 127.0.0.1:8001 oder :8001.

Caddy Berechtigungen

Damit Caddy die statischen Dateien ausliefern kann, braucht der Webserver Schreibrechte. Das ist standardmäßig nicht der Fall. Eine Option ist, caddy der netbox-Gruppe zuzufügen (usermod -aG netbox caddy) und anschließend allen Files unter /var/lib/netbox/static/ (Default ${config.services.netbox.dataDir} == /var/lib/netbox/) Schreibrechte für die Gruppe zu garantieren, z. B. mit chmod -r g+w /var/lib/netbox/static/.

Somit ist die Konfiguration für Caddy abgeschlossen:


# Caddy
services.caddy = {
        enable = true;
        email = ""; # <-- E-Mail Adresse hier einfügen.
        extraConfig = ''
                your.domain.ts.net { # <-- (Tailscale-)Domain hier ersetzen.
                        @notStatic {
                                not path /static/*
                        }
                        reverse_proxy @notStatic [::1]:8001
                        handle_path /static/* {
                                encode gzip zstd
                                root * ${config.services.netbox.dataDir}/static/
                                file_server
                        }
                }
        '';
};

Start

Jetzt müssen wir nur noch die Magie ✨ wirken lassen. Dazu nutzen wir den netten Zauberspruch: nixos-rebuild switch 🧙🏾‍♂️. Alternativ geht auch nixos-rebuild boot + reboot. Vergiss nicht, Tailscale zu starten (tailscale up). 🆙

Tip

Erstelle für den nächsten Service ein LXC-Template. Dann sparst du noch mehr Installationszeit.

Viel Spaß mit Netbox!

Quellen


Info

Dieser Text ist nicht KI generiert.