Arch Linux is my favourite distribution for long time. I can get the benefit of bleeding-edge and highly configurable system, yet still manageable with its lovely package management like pacman, AUR and additionally brew. I also use ansible / chef to manage the workstations / VMs for the sake of immutability. But, managing the playbooks takes time even for doing simple things.
In distrowatch, there are page hit ranks and I already familiar with most of them in the list and I found NixOS there, which I haven’t used. I interested with the declarative configuration approach that NixOS offer to manage the system. Go straight to the documentation rightaway, and try it in VM. Aaaand.. I got overwhelmed at first with the nix language. 30 minutes learning the configurations, finally I can make the configuration usable and enjoying it more than Arch! The offered approach allows us to manage the workstations from zero just by utilizing the nix configurations, so we technically having an immutable system!
Let’s jump in into the configs and take a look what we have:
/etc/nixos/configuration.nixis the default main config/etc/nixos/hardware-configuration.nixis the hardware config (bootloader, kernel, filesystems, additional hardwares)/etc/nixos/home.nixosis thehome-managerflake config, this is optional but I recommend to use its easier to manage the users environment./etc/nixos/flake.nixis the flake config, an experimental feature to simplify and also standardize the writing of nix expressions. It has version-pinned lock which allows reproducibility.
Since I used flake, flake.nix will be the entrypoint.
{
description = "NixOS Workstation";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
home-manager = {
url = "github:nix-community/home-manager/release-24.11";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, nixos-hardware, home-manager, ... }@inputs: {
nixosConfigurations.nixos = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
nixos-hardware.nixosModules.lenovo-thinkpad-x1-6th-gen
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.rizki = import ./home.nix;
home-manager.backupFileExtension = "backup";
}
];
};
};
}
Flake config schema contains two main attributes:
inputsI defined the dependencies for this flake. In above, I used:nixpkgs: provides the packages for nix. You can choose the tags or useunstablefor bleeding-edge release.nixos-hardware: provides the predefined flake config for supported hardwares.home-manager: flake forhome-managergo here for more details
outputsI defined the system configurations with some parameters including modules. There are three modules used in this config.configuration.nix, this is the system definition specifically used by NixOS. Its also importhardware-configuration.nixto define hardware specific configs.configuration.nix
{ config, lib, pkgs, ... }: { imports = [ ./hardware-configuration.nix ]; boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = true; boot.kernelPackages = pkgs.linuxPackages_zen; networking.hostName = "nixos"; networking.networkmanager.enable = true; time.timeZone = "Asia/Jakarta"; i18n.defaultLocale = "en_US.UTF-8"; services.xserver.enable = true; services.xserver.xkb.layout = "us"; services.displayManager.sddm.wayland.enable = true; services.desktopManager.plasma6.enable = true; services.pipewire = { enable = true; pulse.enable = true; }; services.libinput.enable = true; services.fstrim.enable = true; users.users.rizki = { isNormalUser = true; extraGroups = [ "wheel" ]; packages = with pkgs; [ tree ]; shell = pkgs.fish; ignoreShellProgramCheck = true; }; nixpkgs.config.allowUnfree = true; nix.settings.experimental-features = [ "nix-command" "flakes" ]; environment.systemPackages = with pkgs; [ git vim wget kitty libimobiledevice ifuse nss nssTools ]; programs.steam.enable = true; programs.steam.protontricks.enable = true; programs.dconf.enable = true; programs.firefox.enable = true; programs.mtr.enable = true; programs.gnupg.agent = { enable = true; enableSSHSupport = true; }; services.flatpak.enable = true; services.openssh.enable = true; services.btrfs.autoScrub.enable = true; services.btrfs.autoScrub.fileSystems = ["/"]; services.usbmuxd = { enable = true; package = pkgs.usbmuxd2; }; virtualisation.containers.enable = true; virtualisation = { podman = { enable = true; dockerCompat = true; defaultNetwork.settings.dns_enabled = true; }; }; networking.firewall.enable = false; system.stateVersion = "24.11"; }home.nixcontains thehome-managerconfig. I used this to configure the user-level packages and configs in the NixOS.home.nix
{ config, pkgs, ... }: { fonts.fontconfig.enable = true; home.username = "rizki"; home.homeDirectory = "/home/rizki"; home.sessionPath = [ "$HOME/.config/composer/vendor/bin" ]; home.packages = with pkgs; [ (nerdfonts.override { fonts = [ "FiraCode" "DroidSansMono" ]; }) pkgs.localsend pkgs.vlc pkgs.bitwarden-desktop pkgs.bitwarden-cli pkgs.insomnia pkgs.code-cursor pkgs.yakuake pkgs.nodejs_22 pkgs.php84 pkgs.php84Packages.composer pkgs.frankenphp pkgs.qbittorrent pkgs.telegram-desktop pkgs.testdisk pkgs.idevicerestore pkgs.libideviceactivation pkgs.libirecovery pkgs.google-chrome pkgs.podman-desktop pkgs.docker-compose pkgs.kubectl pkgs.qemu_kvm pkgs.virtiofsd pkgs.dbeaver-bin pkgs.act pkgs.gh pkgs.glab pkgs.discord ]; programs.git = { enable = true; userName = "Rizki"; userEmail = "rizki@rizkidoank.com"; }; programs.fish.enable = true; programs.bun.enable = true; programs.java.enable = true; programs.starship.enable = true; programs.starship.enableTransience = true; programs.obs-studio = { enable = true; plugins = with pkgs.obs-studio-plugins; [ wlrobs obs-backgroundremoval obs-pipewire-audio-capture obs-composite-blur obs-3d-effect looking-glass-obs droidcam-obs ]; }; programs.direnv = { enable = true; nix-direnv.enable = true; }; programs.freetube.enable = true; programs.poetry.enable = true; programs.vscode.enable = true; programs.vscode.extensions = [ pkgs.vscode-extensions.rust-lang.rust-analyzer pkgs.vscode-extensions.gitlab.gitlab-workflow pkgs.vscode-extensions.github.vscode-pull-request-github pkgs.vscode-extensions.vue.volar pkgs.vscode-extensions.visualstudioexptteam.vscodeintellicode pkgs.vscode-extensions.ms-vscode.makefile-tools pkgs.vscode-extensions.ms-python.python pkgs.vscode-extensions.ms-python.black-formatter pkgs.vscode-extensions.ms-pyright.pyright pkgs.vscode-extensions.ms-azuretools.vscode-docker pkgs.vscode-extensions.hashicorp.terraform pkgs.vscode-extensions.esbenp.prettier-vscode pkgs.vscode-extensions.dart-code.flutter pkgs.vscode-extensions.ms-kubernetes-tools.vscode-kubernetes-tools pkgs.vscode-extensions.ms-toolsai.jupyter pkgs.vscode-extensions.ms-toolsai.datawrangler pkgs.vscode-extensions.ms-python.vscode-pylance pkgs.vscode-extensions.github.vscode-github-actions ]; services.easyeffects.enable = true; home.stateVersion = "24.11"; programs.home-manager.enable = true; }
When the above configs updated, ensure to rebuild the system to create new generations.
sudo nixos-rebuild switch --flake /etc/nixos/
Using the above configs, I can replicate the configs to multiple workstations with consistent results! With this, no more managing playbooks or cookbook. I can also rollback to any previous generations in case of misconfigurations happened.