Welcome back to the blog. This Post was originally supposed to be a repo and GitLab setup post…but I had to pause. Because I’ve been playing around with something that I think is going to change how I build this entire environment moving forward.
That thing is NixOS. I’m still early in the journey of trying to fully understand it, but what it enables from an automation and reproducibility standpoint fits exactly into what I’ve been trying to build from day one. So before we move forward with the repo setup, I want to take a step back and walk through this. Not just what NixOS is, but why I’m choosing to bring it into this environment.
Understanding the “Why” Behind NixOS
From the beginning of this build, one thing has been clear for me. Is that I want automation. Not partial automation. Not scripts scattered everywhere. I want to be able to define my environment once and recreate it whenever I need it.
That includes:
- My tools
- My configurations
- My system behavior
- Everything
Because let’s be real. We’ve all had that moment where: You set up a system perfectly… everything works… and then something breaks or you need to rebuild it…And now you’re sitting there like: “What did I install again?” That’s exactly what I’m trying to eliminate.
What NixOS Actually Is
At a high level, NixOS is a Linux distribution built around something called the Nix package manager. But here’s the key difference. Instead of installing things step by step…You define your entire system in a configuration file. Your packages, services, users, and system behavior. Everything! And when you’re ready, you rebuild the system based on that config.
Why This Matters
Here’s the part that really clicked for me. Every time you rebuild your system:
- A new version of your system is created
- Your previous versions are still there
- You can roll back instantly if something breaks
That right there changes everything. Because now you’re not scared to experiment. You’re not second guessing installs. You’re not worried about breaking your environment. You can test freely. And if something goes wrong… you just go back.
Reproducibility Is the Goal
Now take that one step further. Once your system is defined the way you want it…That config becomes portable. You can take that same config and deploy it to a new machine and get the exact same setup. Same tools, Same behavior, Same environment. That’s where this ties directly into everything I’ve been doing with versioning and automation. Instead of just versioning application configs…Now I’m versioning the entire system.
The Core Concepts You Need to Understand
Before getting hands-on, these are the key pieces that helped me get started.
Nix (The Engine)
- This is the package manager that handles everything. It installs software in a way that is reproducible and isolated.
The Nix Store
- This is where all installed packages live. Each package is stored with a unique identifier, which is how Nix keeps everything consistent and versioned.
The Nix Language
- This is how you define your system. And yes… this is one of the biggest barriers. You do have to learn it. But this is what gives you the power to describe your entire system in code.
Configuration.nix (The Heart of Everything)
- This is the file that defines your system. If you want something installed, configured, or enabled…It goes here. And when you rebuild your system, this file is what gets applied.
Why This Fits My Environment
For what I’m building, this makes too much sense.
I want:
- A management VM that is consistent
- A system I can rebuild anytime
- A setup that lives in my repo
- Full control over how everything is configured
NixOS gives me all of that.
But Let’s Be Honest… There Are Tradeoffs
This isn’t perfect. And it’s not for everyone. Here are the main things I’ve noticed so far.
1. The Filesystem Is Different
- It doesn’t follow the traditional Linux structure exactly. Some tools expect things in certain locations, and you might have to adjust.
2. You Have to Learn the Nix Language
- There’s no way around it. If you want to really use this properly, you need to understand how to write configs.
3. Package Management Works Differently
There’s no “apt update”.
You:
- Update your config
- Rebuild the system
- Test
Also, packages depend on channels, so you might not always get the latest version unless you change your approach.
The Bottom Line
NixOS gives you Control, Reproducibility, and Confidence when testing. But you pay for it with Complexity and a learning curve. For me, it’s worth it. I’m committed to using this as my main management system and really learning it properly. Because if I get this right…I can rebuild my entire environment anytime without guessing anything.
Command Reference Guide
When you’re ready to actually start building, here are the key commands and workflows. Make sure you watch the video for step by step.
Navigate to NixOS Configuration
- cd /etc/nixos
You’ll see:
- configuration.nix
- hardware-configuration.nix
Edit Your Configuration
- nano configuration.nix
Update your Configuration
# location /etc/nixos/configuration.nix
{ config, pkgs, ... }:
{
##################################################
# Imports
##################################################
imports = [
./hardware-configuration.nix
];
##################################################
# Nix / Package Policy
##################################################
nixpkgs.config.allowUnfree = true;
##################################################
# Bootloader
##################################################
boot.loader.systemd-boot.enable = false;
boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/sda";
##################################################
# Automatic System Updates
##################################################
system.autoUpgrade = {
enable = true;
allowReboot = false;
};
##################################################
# Host / Network
##################################################
networking.hostName = "athena";
networking.networkmanager.enable = true;
# SSH Enable
services.openssh.enable = true;
# SSH Port
networking.firewall.allowedTCPPorts = [ 22 ];
##################################################
# Time / Locale
##################################################
time.timeZone = "America/New_York";
i18n.defaultLocale = "en_US.UTF-8";
##################################################
# Keyboard
##################################################
services.xserver.xkb = {
layout = "us";
variant = "";
};
##################################################
# Desktop / Display
##################################################
services.xserver.enable = true;
# Display manager
services.displayManager.gdm.enable = true;
# Keep Plasma desktop environment
services.desktopManager.gnome.enable = true;
# Enable CUPS to print documents.
services.printing.enable = true;
##################################################
# Audio
##################################################
services.pulseaudio.enable = false;
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
};
##################################################
# Bluetooth
##################################################
hardware.bluetooth.enable = true;
services.blueman.enable = true;
##################################################
# User Account
##################################################
users.users."Testuser" = {
isNormalUser = true;
description = "Test User";
initialpassword = "password";
extraGroups = [ "networkmanager" "wheel" ];
};
##################################################
# Compatibility for dynamically linked binaries
##################################################
programs.nix-ld.enable = true;
##################################################
# Programs
##################################################
programs.firefox.enable = true;
# Neovim installed system-wide.
programs.neovim = {
enable = true;
defaultEditor = true;
viAlias = true;
vimAlias = true;
};
##################################################
# SSH Client Config
##################################################
programs.ssh.extraConfig = ''
Host vm1
HostName 1.1.1.1
User test
Host vm2
HostName 1.1.1.1
User test
Host vm3
HostName 1.1.1.1
User test
'';
##################################################
# System Packages
##################################################
environment.systemPackages = with pkgs; [
################################################
# Work / Communication Tools
################################################
slack
thunderbird
discord
bitwarden-desktop
obsidian
nextcloud-client
google-chrome
################################################
# Development Tools
################################################
git
tmux
ripgrep
fd
fzf
gcc
wl-clipboard
xclip
xsel
python3
python3Packages.pip
nodejs
tree-sitter
lazygit
################################################
# Core CLI Utilities
################################################
curl
wget
tree
rsync
zip
unzip
htop
];
##################################################
# System Version
##################################################
system.stateVersion = "25.11";
}
Rebuild the System
- sudo nixos-rebuild switch
This:
- Applies your config
- Installs packages
- Updates services
If you use the configuration above for testing, make sure to adjust it for your specific environment. Especially your hardware settings.
Final Thoughts
I’m still learning, testing, breaking things and figuring them out. But this is one of those tools that changes how you think about systems. Not just how to install things…But how to design them from the ground up. This is going to play a big role in everything I’m building moving forward. So we’re going to keep going deeper into it. As always… stay curious. I’ll catch you in the next one.