A Beginner’s Guide to the Fundamentals

Basic concepts, components, modules, use cases, and the mental model behind declarative Linux
  1. Part 1 – The Mental Model Behind Declarative Linux
  2. Part 2 – The Nix Language, Nixpkgs, Modules, and OptionsYou are here
  3. Part 3 – The Build Lifecycle, Flakes, and Shells

The Nix language: just enough to read configuration

The Nix language is declarative, functional, lazy, and dynamically typed. You do not need to become a functional-programming expert to use NixOS. Start by learning a small set of recurring structures.

ConceptExampleMeaning
Attribute set{ name = “web”; port = 443; }A set of named values, similar to a map/object.
List[ pkgs.git pkgs.curl ]An ordered collection of values.
Function{ pkgs, … }: { … }Many NixOS modules are functions receiving arguments and returning an attribute set.
let … inlet x = 5; in x + 1Defines local values before returning an expression.
withwith pkgs; [ git curl ]Brings attributes into scope; convenient, although explicit names are often clearer.
String interpolation“Hello ${name}”Inserts an evaluated Nix expression into a string.
Path./hardware.nixA filesystem path value; commonly used for imports.

A few common Nix language features in one small example.

let
  appName = “demo”;
in {
  environment.systemPackages = with pkgs; [
    git
    curl
  ];
  environment.variables.APP_NAME = appName;
}

The important beginner habit is to read Nix as data composition rather than as a shell script. Order often matters less than dependency relationships and option merging.

Nixpkgs: packages, libraries, and modules in one enormous ecosystem

Nixpkgs is the central repository most Nix users interact with. It contains package definitions for a huge software ecosystem, helper functions for writing Nix code, and the NixOS modules that expose many operating-system options.

When you write pkgs.git, pkgs.postgresql, or pkgs.python3, you are normally selecting package definitions from Nixpkgs. When you write services.openssh.enable, you are using an option provided by a NixOS module that also lives in the broader Nixpkgs source tree.

Package versus module

A package is software you can build or install. A NixOS module is configuration logic that declares and implements system options. A module may use packages internally, but the two concepts are different.

The NixOS module system: how a large configuration stays composable

A real operating system configuration is too large to keep in one file. NixOS solves this through modules. A module can define options, import other modules, and contribute configuration to the final merged system.

A configuration composed from several modules.

{ config, pkgs, … }:
{
  imports = [
    ./hardware-configuration.nix
    ./networking.nix
    ./developer-tools.nix
  ];

  services.openssh.enable = true;
}

One of the most powerful aspects of the module system is merging. Multiple modules can contribute values to the same option when that option’s type supports merging. For example, two modules can both add packages to environment.systemPackages and NixOS can combine the lists.

Module roleTypical examples
HardwareBoot loader, kernel modules, GPU support, disks, filesystems.
Base systemUsers, locale, time zone, packages, SSH, firewall.
RoleWeb server, database server, developer workstation, CI runner.
ApplicationA service definition, environment variables, ports, dependencies.
EnvironmentDevelopment-only tools, QA instrumentation, staging settings, production hardening.
HostMachine-specific hostname, disks, hardware IDs, role selection.

Options: the public interface of a NixOS module

NixOS options are typed configuration fields such as services.openssh.enable, networking.firewall.allowedTCPPorts, or time.timeZone. Modules declare options and define what those options cause the system to build or activate.

For beginners, the NixOS option search and the NixOS manual are often more useful than guessing. If you want to enable a service, first look for an existing module and its options before writing custom systemd units yourself.

Anatomy of a basic NixOS configuration

A deliberately small example. Your generated hardware configuration is normally kept separate.

{ config, pkgs, … }:
{
  imports = [ ./hardware-configuration.nix ];

  networking.hostName = “beginner-box”;
  time.timeZone = “Australia/Sydney”;

  users.users.alice = {
    isNormalUser = true;
    extraGroups = [ “wheel” “networkmanager” ];
  };

  environment.systemPackages = with pkgs; [
    git
    vim
    htop
  ];

  services.openssh.enable = true;
  networking.firewall.allowedTCPPorts = [ 22 ];

  system.stateVersion = “26.05”;
}

There is nothing magical about the filename configuration.nix. It is simply the conventional main module used by a traditional NixOS setup. That module can import as many other files as you need.

About system.stateVersion

This setting is a compatibility boundary for defaults that may change across NixOS releases. It is not the same thing as “which NixOS version am I running?” Do not automatically bump it during every upgrade without reading the release guidance.

What can NixOS manage?

A NixOS configuration can describe a very broad range of operating-system concerns. The following categories are a useful map for beginners.

AreaExamples of declarative state
Boot & kernelBoot loader, kernel packages, kernel parameters, initrd modules.
HardwareGPU drivers, firmware, filesystems, device-specific modules.
Users & securityUsers, groups, sudo, SSH, PAM, certificates, firewall rules.
NetworkingHostnames, interfaces, DNS, VPNs, routes, firewall ports.
PackagesSystem packages, package overrides, overlays, custom packages.
ServicesOpenSSH, PostgreSQL, Nginx, Docker/Podman, monitoring, databases, many application services.
DesktopDisplay server, desktop environments, fonts, audio, printing.
Schedulingsystemd services, timers, cron-like jobs, garbage collection.
VirtualizationContainers, VMs, Docker, Podman, libvirt and related tooling.