

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.
| Concept | Example | Meaning |
| 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 … in | let x = 5; in x + 1 | Defines local values before returning an expression. |
| with | with 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.nix | A 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 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.
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 role | Typical examples |
| Hardware | Boot loader, kernel modules, GPU support, disks, filesystems. |
| Base system | Users, locale, time zone, packages, SSH, firewall. |
| Role | Web server, database server, developer workstation, CI runner. |
| Application | A service definition, environment variables, ports, dependencies. |
| Environment | Development-only tools, QA instrumentation, staging settings, production hardening. |
| Host | Machine-specific hostname, disks, hardware IDs, role selection. |
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.
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.
Next: Part 3 — The Nix Language, Nixpkgs, Modules, and Options