Building_your_config_as_a_package
Building your configuration as a Package

TL;DR This post demonstrates other ways to modularize your config as well as going into more advanced outputs.
This allows you to build your configuration as a package allowing you to separate the process of creating a configuration artifact and applying it to the live system giving you a reusable artifact that can be used to deploy to different systems. This can make it easier to isolate it from other parts of your system making debugging easier.
The following is a snip of my flake.nix:
# flake.nix
outputs = my-inputs @ {
self,
nixpkgs,
treefmt-nix,
...
}: let
system = "x86_64-linux";
host = "magic";
userVars = {
username = "jr";
gitUsername = "TSawyer87";
editor = "hx";
term = "ghostty";
keys = "us";
browser = "firefox";
flake = builtins.getEnv "HOME" + "/my-nixos";
};
inputs =
my-inputs
// {
pkgs = import inputs.nixpkgs {
inherit system;
};
lib = {
overlays = import ./lib/overlay.nix;
nixOsModules = import ./nixos;
homeModules = import ./home;
inherit system;
};
};
defaultConfig = import ./hosts/magic {
inherit inputs;
};
in {
packages.${system} = {
nixos = defaultConfig.config.system.build.toplevel;
};
# NixOS configuration
nixosConfigurations.${host} = lib.nixosSystem {
inherit system;
specialArgs = {
inherit inputs system host userVars;
};
modules = [
./hosts/${host}/configuration.nix
];
};
};
}
I didn’t want to change the name of
inputsand effect other areas of my config so I first renamed@ inputsto@ my-inputsto make the merged attribute set use the originalinputsname.Note, I’m still using home-manager as a module I just had to move it for all modules to be available inside the artifact built with
nix build .#nixos
Benefits of nixosConfiguration as a Package
packages.x86_64-linux.nixos = self.nixosConfigurations.magic.config.system.build.toplevel;
- The above expression exposes the
toplevelderivation ofnixosConfiguration.magicas a package, which is the complete system closure of your NixOS configuration.
Here is the /hosts/magic/default.nix:
# default.nix
{inputs, ...}:
inputs.nixpkgs.lib.nixosSystem {
inherit (inputs.lib) system;
specialArgs = {inherit inputs;};
modules = [./configuration.nix];
}
- Because we want all modules, not just NixOS modules this requires changing
your
configuration.nixto include your home-manager configuration. The core reason for this is that thepackages.nixosoutput builds a NixOS system, and home-manager needs to be a part of that system’s definition to be included in the build.
# configuration.nix
{
pkgs,
inputs,
host,
system,
userVars,
...
}: {
imports = [
./hardware.nix
./security.nix
./users.nix
inputs.lib.nixOsModules
# inputs.nixos-hardware.nixosModules.common-gpu-amd
inputs.nixos-hardware.nixosModules.common-cpu-amd
inputs.stylix.nixosModules.stylix
inputs.home-manager.nixosModules.home-manager
];
# Home-Manager Configuration needs to be here for home.packages to be available in the Configuration Package and VM i.e. `nix build .#nixos`
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
extraSpecialArgs = {inherit pkgs inputs host system userVars;};
users.jr = {...}: {
imports = [
inputs.lib.homeModules
./home.nix
];
};
};
############################################################################
nixpkgs.overlays = [inputs.lib.overlays];
[!NOTE]:
inputs.lib.nixOsModulesis equivalent to../../homein my case and imports all of my nixOS modules. This comes from theflake.nixwhere I havenixOsModules = import ./nixosWhich looks for adefault.nixin thenixosdirectory.
My ~/my-nixos/nixos/default.nix looks like this:
# default.nix
{...}: {
imports = [
./drivers
./boot.nix
./utils.nix
#..snip..
];
}
Usage and Deployment
To build the package configuration run:
nix build .#nixos
sudo ./result/bin/switch-to-configuration switch
Adding a Configuration VM Output
Building on what we already have, add this under defaultConfig:
defaultConfig = import ./hosts/magic {
inherit inputs;
};
vmConfig = import ./lib/vms/nixos-vm.nix {
nixosConfiguration = defaultConfig;
inherit inputs;
};
and under the line nixos = defaultConfig.config.system.build.toplevel add:
packages.${system} = {
# build and deploy with `nix build .#nixos`
nixos = defaultConfig.config.system.build.toplevel;
# Explicitly named Vm Configuration `nix build .#nixos-vm`
nixos-vm = vmConfig.config.system.build.vm;
}
And in lib/vms/nixos-vm.nix:
# nixos-vm.nix
{
inputs,
nixosConfiguration,
...
}:
nixosConfiguration.extendModules {
modules = [
(
{pkgs, ...}: {
virtualisation.vmVariant = {
virtualisation.forwardPorts = [
{
from = "host";
host.port = 2222;
guest.port = 22;
}
];
imports = [
inputs.nixos-hardware.nixosModules.common-gpu-amd
# hydenix-inputs.nixos-hardware.nixosModules.common-cpu-intel
];
virtualisation = {
memorySize = 8192;
cores = 6;
diskSize = 20480;
qemu = {
options = [
"-device virtio-vga-gl"
"-display gtk,gl=on,grab-on-hover=on"
"-usb -device usb-tablet"
"-cpu host"
"-enable-kvm"
"-machine q35,accel=kvm"
"-device intel-iommu"
"-device ich9-intel-hda"
"-device hda-output"
"-vga none"
];
};
};
#! you can set this to skip login for sddm
# services.displayManager.autoLogin = {
# enable = true;
# user = "jr";
# };
services.xserver = {
videoDrivers = [
"virtio"
];
};
system.stateVersion = "24.11";
};
# Enable SSH server
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = true;
};
};
virtualisation.libvirtd.enable = true;
environment.systemPackages = with pkgs; [
open-vm-tools
spice-gtk
spice-vdagent
spice
];
services.qemuGuest.enable = true;
services.spice-vdagentd = {
enable = true;
};
hardware.graphics.enable = true;
# Enable verbose logging for home-manager
# home-manager.verbose = true;
}
)
];
}
- Uncomment and add your username to auto login.
And an apps output that will build and deploy in one step with
nix build .#deploy-nixos I’ll show packages and apps outputs for
context:
# flake.nix
# Default package for tools
packages.${system} = {
default = pkgs.buildEnv {
name = "default-tools";
paths = with pkgs; [helix git ripgrep nh];
};
# build and deploy with `nix build .#nixos`
nixos = defaultConfig.config.system.build.toplevel;
# Explicitly named Vm Configuration `nix build .#nixos-vm`
nixos-vm = vmConfig.config.system.build.vm;
};
apps.${system}.deploy-nixos = {
type = "app";
program = toString (pkgs.writeScript "deploy-nixos" ''
#!/bin/sh
nix build .#nixos
sudo ./result/bin/switch-to-configuration switch
'');
meta = {
description = "Build and deploy NixOS configuration using nix build";
license = lib.licenses.mit;
maintainers = [
{
name = userVars.gitUsername;
email = userVars.gitEmail;
}
];
};
};
Debugging
- Before switching configurations, verify what’s inside your built package:
nix build .#nixos --dry-run
nix build .#nixos-vm --dry-run
nix show-derivation .#nixos
- Explore the Package Contents
Once the build completes, you get a store path like
/nix/store/...-nixos-system. You can explore the contents using:
nix path-info -r .#nixos
tree ./result
ls -lh ./result/bin
Instead of switching, test components:
nix run .#nixos --help
nix run .#nixos --version
Load the flake into the repl:
nixos-rebuild repl --flake .
nix-repl> flake.inputs
nix-repl> config.fonts.packages
nix-repl> config.system.build.toplevel
nix-repl> config.services.smartd.enable # true/false
nix-repl> flake.nixosConfigurations.nixos # confirm the built package
nix-repl> flake.nixosConfigurations.magic # Inspect host-specific config
- You can make a change to your configuration while in the repl and reload with
:r
Understanding Atomicity
Atomicity means that a system update (e.g. changing
configuration.nixor a flake-basedtoplevelpackage) either fully succeeds or leaves the system unchanged, preventing partial or inconsistent states.The
toplevelpackage is the entry point for your entire NixOS system, including the kernel, initrd, system services, andhome-managersettings.Building with
nix build .#nixoscreates thetoplevelderivation upfront, allowing you to inspect or copy it before activation:
nix build .#nixos
ls -l result
- In contrast,
nixos-rebuild switchbuilds and activates in one step, similar tocargo runalthough both do involve the sametoplevelderivation.
The toplevel package can be copied to another NixOS machine:
nix build .#nixos
nix copy ./result --to ssh://jr@server
# or for the vm
nix build .#nixos-vm
nix copy .#nixos-vm --to ssh://jr@server
# activate the server
ssh jr@server
sudo /nix/store/...-nixos-system-magic/bin/switch-to-configuration switch