Setting Up a New HomeLab Server

· Y3K.dev

A brief story about how I upgraded my basic homelab.

Table of Contents

Background #

I'm someone who's deeply focused on privacy, love FOSS, and hate the idea of hosting my private data (mostly family photos) on third‑party systems, you know what they say: "If they don't charge you for the product, you are the product". I've also heard horror stories about people losing access to their Google accounts without cause and, even worse, having no way to appeal the ban.

For a long time, I would dump all our family's media onto an external HDD and mirror it to another. That worked well for a while, until I grew tired of managing the media dumps, and they grew tired of asking for access every time they wanted to see old pictures.

Self‑hosting my photo library had interested me for some time. I even picked up a couple of Odroid boards to experiment, but the lack of official Linux support and uneven ARM compatibility made them impractical for sustained use.

Enter the Optiplex #

About a year ago, I stumbled on a second‑hand Dell OptiPlex with an Intel Core i7-9700T, 32 GB of RAM, and a compact x86_64 form factor. It wasn't shiny new, but it promised rock‑solid Linux compatibility and outperformed my little Odroids.

Since this was my first real home server, I kept things simple: Ubuntu, a VPN, Docker. For photos, I gave PhotoPrism a spin and liked it, though the free tier only supports one user and the mobile app felt undercooked. Manual imports were a bit of a drag.

Then I discovered Immich, which fit the bill much better: smooth iOS/Android apps let everyone back up directly to the server.

That combo (OptiPlex, VPN, Immich) ran like a champ for six months. Along the way, I added Navidrome for music, spun up a Kanboard instance for task tracking, and even started doing remote dev on the machine.

Upgrading Hardware #

Recently I scored a second‑hand Lenovo ThinkCentre with an Intel Core i7-10700T, 32 GB RAM, and a 1 TB NVMe SSD. It was a steal, so I figured I'd use it for public‑facing stuff like a blog, git server, maybe even a Minecraft server; while the OptiPlex handled private services. But then I realized the OptiPlex still had more life in it, so I'm swapping roles: ThinkCentre becomes the new lab server.

So here I am, building a fresh setup from scratch. My goals are the same: privacy, portability, easy maintenance, but I want better isolation and fewer manual tweaks. These are the paths I've weighed:

Ubuntu with Docker #

Proxmox with VMs #

I tested Proxmox (my CPU has KVM support). It works solidly, but the UI and storage layers felt like overkill for my needs.

Ubuntu Host + Fedora CoreOS VM #

Running Fedora CoreOS inside QEMU/KVM on Ubuntu gave me clean separation, but managing KVM configs felt like extra overhead.

NixOS with Docker & KVM #

I've been eyeing NixOS for a while. The declarative model appeals to me "one config file to rule them all" they say and its minimal runtime footprint seems ideal.


Installing NixOS #

I've never set up NixOS before, so expect a few first‑time notes. I'll keep the deep dives to a minimum but capture the essentials.

Preparing the Install Media #

I grabbed an old trusty USB stick (been using it for years):

1$ curl -LO https://channels.nixos.org/nixos-24.11/latest-nixos-minimal-x86_64-linux.iso
2$ sudo dd if=latest-nixos-minimal-x86_64-linux.iso of=/dev/sda bs=4M conv=fsync status=progress

Booting the Live Environment #

Boot via UEFI, pick the default live option, and you'll land at a nixos@ shell. ip a shows DHCP is up. To SSH in:

1[nixos@nixos:~]$ mkdir -p .ssh && curl -L https://github.com/y3k.keys -o .ssh/authorized_keys

Then, from my laptop:

1$ ssh -o "StrictHostKeyChecking no" nixos@<lan-IP>

Preparing the Storage Media #

Since I'm following the NixOS manual I'll just use what they recommend on their partitioning section.

First I need to identify what device I'll use:

 1[nixos@nixos:~]$ lsblk
 2NAME            MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
 3loop0             7:0    0   1.1G  1 loop /nix/.ro-store
 4sda               8:0    1  14.4G  0 disk
 5├─sda1            8:1    1   1.2G  0 part /iso
 6└─sda2            8:2    1     3M  0 part
 7nvme0n1         259:0    0 953.9G  0 disk
 8├─nvme0n1p1     259:1    0     1G  0 part
 9├─nvme0n1p2     259:2    0     2G  0 part
10└─nvme0n1p3     259:3    0 950.8G  0 part
11  ├─vg-lv--root 254:0    0    32G  0 lvm
12  ├─vg-lv--swap 254:1    0     8G  0 lvm
13  ├─vg-lv--var  254:2    0   256G  0 lvm
14  └─vg-lv--home 254:3    0 654.8G  0 lvm

The /dev/sda device is my USB stick, and I plan to install NixOS into the NVMe therefore I'll use nvme0n1 from now on.

Partitions and LVM #

The manual recommends to have a /boot partition marked with ESP, a /swap partition and a / root one for everything else, I didn't quite like this setup so I'll go with something more similar to what I had before.

Note: Looks like having a single ESP partition as /boot is a requirement for NixOS, therefore I merged /boot and /boot/efi into a single partition marked as ESP.

 1[nixos@nixos:~]$ sudo -i
 2
 3[root@nixos:~]# parted /dev/nvme0n1 -- mklabel gpt
 4Warning: The existing disk label on /dev/nvme0n1 will be destroyed and all data on this disk will
 5be lost. Do you want to continue?
 6Yes/No? yes
 7
 8[root@nixos:~]# parted /dev/nvme0n1 -- mkpart ESP fat32 1MB 1G set 1 esp on
 9
10[root@nixos:~]# parted /dev/nvme0n1 -- mkpart lvm 1GiB 100% set 2 lvm on
11
12[root@nixos:~]# parted /dev/nvme0n1 --list
13Model: SAMSUNG MZVLB1T0HBLR-00000 (nvme)
14Disk /dev/nvme0n1: 1024GB
15Sector size (logical/physical): 512B/512B
16Partition Table: gpt
17Disk Flags:
18
19Number  Start   End     Size    File system  Name  Flags
20 1      1049kB  1000MB  999MB   ext4         ESP   boot, esp
21 2      1074MB  1024GB  1023GB  fat32        lvm   lvm
22
23[root@nixos:~]# pvcreate /dev/nvme0n1p2
24  Physical volume "/dev/nvme0n1p2" successfully created.
25
26[root@nixos:~]# vgcreate vg /dev/nvme0n1p2
27  Volume group "vg" successfully created
28
29[root@nixos:~]# lvcreate -n swap -L 8G vg
30  Logical volume "swap" created.
31
32[root@nixos:~]# lvcreate -n root -L 64G vg
33  Logical volume "root" created.
34
35[root@nixos:~]# lvcreate -n home -l 100%FREE vg
36  Logical volume "home" created.
37
38[root@nixos:~]# mkfs.fat -F32 -n boot /dev/nvme0n1p1
39mkfs.fat 4.2 (2021-01-31)
40
41[root@nixos:~]# mkswap -L swap /dev/vg/swap
42Setting up swapspace version 1, size = 8 GiB (8589930496 bytes)
43LABEL=swap, UUID=e788698e-fd2b-465d-ad23-0c0d99a10bb5
44
45[root@nixos:~]# mkfs.ext4 -L nixos /dev/vg/root
46mke2fs 1.47.1 (20-May-2024)
47
48[root@nixos:~]# mkfs.ext4 -L home /dev/vg/home
49mke2fs 1.47.1 (20-May-2024)

Mounting everything #

 1[root@nixos:~]# mount /dev/vg/root /mnt/
 2
 3[root@nixos:~]# mkdir /mnt/boot
 4
 5[root@nixos:~]# mount /dev/nvme0n1p1 /mnt/boot/
 6
 7[root@nixos:~]# swapon /dev/vg/swap
 8
 9[root@nixos:~]# mkdir /mnt/home
10
11[root@nixos:~]# mount /dev/vg/home /mnt/home/
12
13[root@nixos:~]# lsblk
14NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
15loop0         7:0    0   1.1G  1 loop /nix/.ro-store
16sda           8:0    1  14.4G  0 disk
17├─sda1        8:1    1   1.2G  0 part /iso
18└─sda2        8:2    1     3M  0 part
19nvme0n1     259:0    0 953.9G  0 disk
20├─nvme0n1p1 259:1    0   953M  0 part /mnt/boot
21└─nvme0n1p2 259:2    0 952.9G  0 part
22  ├─vg-swap 254:0    0     8G  0 lvm  [SWAP]
23  ├─vg-root 254:1    0    64G  0 lvm  /mnt
24  └─vg-home 254:2    0 880.9G  0 lvm  /mnt/home

Installing NixOS #

Once the storage device is set, we can finally give it a shot the installer.

1[root@nixos:~]# nixos-generate-config --root /mnt/
2writing /mnt/etc/nixos/hardware-configuration.nix...
3writing /mnt/etc/nixos/configuration.nix...
4For more hardware-specific settings, see https://github.com/NixOS/nixos-hardware.

This is my first time using Nix so the configuration file is a little bit daunting, I opened it with vim and modified the options that I thought were useful like:

Once the first configuration was set, it was time to run the install command:

 1[root@nixos:~]# nixos-install
 2copying channel...
 3building the configuration in /mnt/etc/nixos/configuration.nix...
 4...
 5installation finished!
 6
 7[root@nixos:~]# nixos-enter --root /mnt/ -c 'passwd y3k'
 8setting up /etc...
 9New password:
10Retype new password:
11passwd: password updated successfully
12
13[root@nixos:~]# reboot

Getting started with NixOS #

Everything seemed to be successful and I was greeted by the login screen and I could access with my normal user and password. My $HOME was totally empty though, it was time to start setting this up to my liking, my first step was to ensure I could SSH into the server so I created my .ssh directory and downloaded my public keys from GitHub again.

NeoVim as editor #

NixOS seems to ship nano as the only editor, but I prefer neovim so I used this opportunity to install my first package, adding the next config to the configuration.nix file.

  environment.systemPackages = with pkgs; [
    neovim
  ];

  environment.variables = {
    EDITOR = "nvim";
  };

Static IP #

I like to use static IPs for my home lab servers, this time was not going to be an exception, searching for a bit I found it how to configure it in the nix file.

networking = {
  ...
  interfaces.eno1 = {
    useDHCP = false;
    ipv4.addresses = [{
      address = "192.168.2.6";
      prefixLength = 24;
    }];
  };
  ...
};

Installing zsh with OhMyZsh #

programs.zsh = {
  enable = true;
  ohMyZsh = {
    enable = true;
    plugins = [ "common-aliases" ];
    theme = "fishy";
  };
};

After rebuilding the system I'm still able to SSH into my server with a static IP and over Tailscale, with tmux, using zsh as my shell and also neovim available.

Using configuration.nix is so different from any other package manager that I've used before like apt or pacman, I know I'm barely scratching the surface but I'm starting to really like this approach, hopefully I won't regret it once I start hitting edge cases.