NixOS Quickstart
This is a guide I wrote for a friend to help with getting started in NixOS. The hope is to detail everything a reasonably experienced Linux user needs to know to get started.
It's not really finished, but I have put it here anyway. If you have any questions or suggestions for how to make this better, please contact us.
Installation
All of the commands in this section should be run as root with
sudo
.
Partitioning, Formatting, and Mounting
-
Partition your disk(s) like you would any other system. The usual collection of partitioning tools are provided. (I recommend
cfdisk
.) -
Format your partitions using the various
mkfs
commands. LVM volumes andmdadm
software RAID are also supported. -
Mount your partitions in
/mnt
the same way you want the system to mount them in/
. Enable any swap devices you've created withswapon
.
UEFI systems should use a GPT disk containing a >=512MiB EFI System partition. It should be formatted FAT32 (
mkfs.fat -F 32
) and mounted as/boot
.
Installing
Before installing, you need a file /mnt/etc/nixos/configuration.nix
which
describes your system configuration. In later sections we will discuss this
configuration, but for now you can generate a reasonable default like so:
nixos-generate-config --root /mnt
You can have a look at the resulting configuration if you'd like a sneak peek.
(nano
is provided for text editing.) For now, the defaults should be fine.
Once you're ready to install:
nixos-install
After the install finishes, you'll be asked for a password you can use to login as root
after rebooting.
see also NixOS Manual: Part 1. Installation
The Nix Expression Language
Nix is a lazy, purely functional language which you'll be using to configure your system. Before we discuss configuration in detail, you should be familiar with its basic syntax.
Types
- Strings:
"Hello, world!" ''First Line Second Line ''
- Integers and Floating-Point Numbers:
1 3.14
- Filesystem Paths:
/path/to/file ./hardware-configuration.nix ./. #! current directory: just `./` is not valid #! paths in angle brackets search the contents of $NIX_PATH. $NIX_PATH can #! have {key}={value} entries for special prefixes. #! `<nixpkgs>` is the path to your system's local copy of `nixpkgs`, located #! at `/nix/var/nix/profiles/per-user/root/channels/nixos`, <nixpkgs/pkgs/tools/misc/cowsay> #! and `<nixos-config>` is the path `/etc/nixos/configuration.nix` <nixos-config>
- URLs (don't use these, they're
deprecated!):
https://github.com/NixOS/rfcs/blob/master/rfcs/0045-deprecate-url-syntax.md
- Booleans:
true false
- Null:
null;
- Lists, whose values can be any type:
[ 1 "two" [ 3.0 ] { four = 4; } ]
- Sets (like dicts, maps), whose keys are strings and whose values can be any
type:
{ a = 1; b = [ 2 ]; c = { x = 3; }; } #! keys can be quoted if they have special characters that can't be there #! normally { "blah..." = 1; } #! make sure you don't forget those semicolons!
- Functions, which take exactly one argument and return the result of exactly
one expression:
#! these commands are performed in `nix repl` if you want to try them yourself. f = n: n + 1 x = f 1 #: 2 #! you can use currying to create functions which take multiple arguments f = first: second: first + second x = f 1 2 #: 3 #! or you can destructure a set argument in the function declaration like so f = { first, second }: first + second x = f { first = 1; second = 2; } #: 3 #! you can also accept a set with unspecified extra attributes (...), and #! specify defaults for optional attributes (attrib ? default) f = { first, second ? 2, ... }: first + second x = f { first = 1; irrelevant = 5; } #: 3 #! and even access the whole set by name to get to those extra attributes #! (name@{ ... } or { ... }@name) f = input@{ first, second, ... }: first + second + input.third x = f { first = 1; second = 2; third = 3; } #: 6
Operators
set.x # attribute access
f x # function application
-x # numerical negation
# attribute existence testing (true if `set` contains an attribute `x`)
set ? x
list ++ list # list concatenation
x * y / z # multiplication and division
x + y - z # addition and subtraction
!a # logical negation
# set merging (a set with both sets' attributes, if both sets have an attribute
# with the same key then `set2` takes precedence)
set1 // set2
a<b<=c>=d>e # comparison
a == b != c # equality testing
a && b # logical conjunction
a || b # logical disjunction
# logical implication (if `a` is true, `b` needs to be true or else the result
# is false)
a -> b
Builtins
abort s # no return value. stop evaluation and print error string
baseNameOf s # part of `s` after its last '/' character
derivation inputs # a derivation. will be discussed later
dirOf s # part of `s` preceding `baseNameOf s`
fetchTarball url # path of unpacked .tar.(gz|xz|bz2) downloaded from `url`
import path # the expression at `path`
# whether `x` is null. this is deprecated because you can just write `x == null`
isNull x
map f list # list of each `f x` for each `x` in `list`
removeAttrs set list # `set` with each attribute in `list` removed
throw s # no return value. `abort s` but softer
toString x # `x` converted to a string
builtins # a set containing more builtins
The remaining built-in functions are kept in builtins
to prevent clutter. The
contents of that set are described in Nix Manual: 15.5. Built-in
Functions.
Other Features
# comments, as you've probably noticed, begin like this
/* multi-line comments, however,
use these */
# bring a set's attributes into scope
s = { a = 1; };
x = with s; [ a ]; #: [ 1 ]
# define some temporary local variables to use in an expression
x = let
a = 1;
b = 2;
in a + b; #: 3
# allow a set to reference its own attributes
x = rec {
a = 1;
b = a + 1;
}; #: { a = 1; b = 2; }
# inherit some variables from the surrounding scope
x = rec {
a = 1;
b = {
inherit a;
}
}; #: { a = 1; b = { a = 1; }; }
# or from another set
x = rec {
a = { b = 1; };
inherit (a) b;
}; #: { a = { b = 1; }; b = 1; }
see also Nix Manual: Part IV. Writing Nix Expressions or NixOS Wiki: Nix Expression Language
Modules
A .nix
file contains a single expression, which means it can be evaluated to a
single value. A module is a particular kind of Nix expression which can
- import other modules,
- declare a set of options you can change to configure its behaviour,
- and define a set of options that were declared in other modules.
It's a set, or a function that returns a set, that can look like this:
{ ... }: {
imports = [
# import paths
];
options = {
# option declarations
};
config = {
# option definitions
};
}
If it doesn't need to declare any options, it can also look like this:
{ ... }: {
imports = [
# import paths
];
# option definitions
}
Or, if it doesn't need any imports and takes no arguments, it can even look like this:
{
# option definitions
}
Import paths can lead to modules themselves, or to directories (in which case
the module named default.nix
inside that directory is imported).
If the module is a function, it's passed a set with the attributes
config
, the set of all option definitions for the system,options
, the set of all option declarations for the system,pkgs
, the set of all packages from<nixpkgs/pkgs>
lib
, the set of utility functions from<nixpkgs/lib>
, which are documented in Nixpkgs Manual: 5.1. Nixpkgs Library Functions,- and
modulesPath
, the path<nixpkgs/nixos/modules>
.
Modules are the building block for configuration in NixOS. Each system's configured state is defined by a set of option definitions.
configuration.nix by Example
Your configuration.nix
is a module. It defines some of the options declared in
modules elsewhere, usually for the most part ones in
<nixpkgs/nixos/modules>
.
You can search for information about these options on this
page.
When you alter your configuration, you can type nixos-rebuild switch
to switch
to it and make it the new default. You can also check the NixOS
Commands page for different ways to use
nixos-rebuild
.
What follows are some example configurations to hopefully give you some helpful context for how NixOS modules are configured.
A Basic Desktop
# configuration.nix
{ pkgs, ... }: {
imports = [
./hardware-configuration.nix
];
boot.loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};
networking = {
# it's recommended to disable DHCP globally and enable it seperately for
# each interface
useDHCP = false;
interfaces.enp1s0.useDHCP = true;
};
services = {
openssh.enable = true;
xserver = {
enable = true;
desktopManager.gnome3.enable = true;
# NixOS's default display manager is LightDM. however, GDM fits better
# with GNOME
displayManager.gdm.enable = true;
};
};
users = {
# disallows users and groups from being modified outside of the system
# configuration, which is useful if you only ever want to configure users
# inside your `configuration.nix`
mutableUsers = false;
users.raccoon = {
# regular interactive user with a home directory etc.
isNormalUser = true;
# can use `sudo`
extraGroups = [ "wheel" ];
# you can generate a hashed password for this option with
# `mkpasswd -m sha-512`
hashedPassword = "/*snip*/";
# note the `with pkgs;` so you don't have to type `pkgs.vim` etc. for
# each package
packages = with pkgs; [ vim ];
};
};
system.stateVersion = "20.09";
}
This configuration is based on the default generated one, with a few simple additions. It will give you
- the
systemd-boot
bootloader, - a user named
raccoon
withvim
in their environment and the permission to usesudo
, - DHCP for
enp1s0
, - an ssh service,
- and a GNOME 3 desktop.
Note that I've omitted the contents of hardware-configuration.nix
. This file
includes some important hardware-dependent configuration such as necessary
filesystem mounts and kernel modules. Apart from being created automatically, it
is not treated specially; you could specify all of those things yourself, too.
A Static Webserver
This is a configuration for a webserver that serves static files from
/srv/www/<subdomain>
. The first part includes the configuration.nix
itself
and some hardware configuration for a system running on a Linode. The second part configures the actual webserver, and uses a little custom function in the process.
Part 1
# configuration.nix
{
imports = [
./boot.nix
./fs.nix
./net.nix
./users.nix
./web
]
services.openssh.enable = true;
system.stateVersion = "20.09";
}
# boot.nix
{ modulesPath, ... }: {
imports = [
# some defaults for virtual machines
(modulesPath + "/profiles/qemu-guest.nix")
];
boot = {
# serial console for Linode's web shell
kernelParams = [ "console=ttyS0" ];
# for SATA and SCSI
initrd.availableKernelModules = [ "ahci" "sd_mod" ];
loader.grub = {
enable = true;
# configure but don't install GRUB (Linode already manages it)
device = "nodev";
# put kernel in /boot
copyKernels = true;
# use partition labels, drive UUIDs can change
fsIdentifier = "label";
extraConfig = "serial; terminal_input serial; terminal_output serial";
};
};
}
# fs.nix
{
fileSystems = {
"/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
};
"/srv" = {
device = "/dev/disk/by-label/srv";
fsType = "ext4";
};
};
swapDevices = [
{ device = "/dev/disk/by-label/swap"; }
];
}
# net.nix
{
networking = {
useDHCP = false;
# the physical location of the same network interface can change between
# boots, use `ethX` instead of `enpXsY` for interface names
usePredictableInterfaceNames = false;
interfaces.eth0.useDHCP = true;
};
}
# users.nix
{
users = {
mutableUsers = false;
users.admin = {
isNormalUser = true;
extraGroups = [ "wheel" ];
hashedPassword = "/*snip*/";
# ssh auto-login with public key
openssh.authorizedKeys.keys = [
"/*snip*/"
];
};
};
}
Part 2
In this part we'll set up nginx to use TLS and host two subdomains: www
and
files
. We'll write a function to help us configure both subdomains the same
way without repeating ourselves.
# web/default.nix
{
imports = [
./www.nix
./files.nix
];
networking.firewall.allowedTCPPorts = [ 80 443 ];
# use ACME to get certificates for TLS (with Let's Encrypt as the default CA)
security.acme = {
acceptTerms = true;
email = "me@example.com";
};
services.nginx = {
enable = true;
# by default, nginx will restart instead of reload on config change
enableReload = true;
# some sane defaults
recommendedOptimisation = true;
recommendedTlsSettings = true;
recommendedGzipSettings = true;
recommendedProxySettings = true;
};
}
# web/vhost.nix
# this is a function that returns the settings we want each subdomain to have by
# default, to help us not repeat ourselves
sub: let
domain = "example.com";
root = /srv/www;
# notice how we can nest `let ... in`. it's helpful for temp values.
# we need `domain` and `root` to define `subdomain` and `subroot` but we don't
# need them elsewhere
in let
subdomain = if sub == null
then domain
else "${sub}.${domain}";
subroot = if sub == null
then root
else "${root}/${sub}";
# using `rec` because `definition` references `name` and `settings`
in rec {
name = subdomain;
settings = {
forceSSL = true;
enableACME = true;
root = "${subroot}";
locations."/".tryFiles = "$uri $uri/ =404";
};
# set that looks like `services.nginx.virtualHosts` for convenience
definition = { ${name} = settings; };
}
# web/www.nix
let
vhost = import ./vhost.nix;
in let
root = vhost null;
www = vhost "www";
in {
services.nginx.virtualHosts = www.definition // {
# make `example.com` redirect to `www.example.com`
"${root.name}" = root.settings // {
locations."/".return = "301 https://${www.name}$request_uri";
};
};
}
# web/files.nix
{ services.nginx.virtualHosts = (import ./vhost.nix "files").definition; }
Modules with Declarations
In this section, we'll look at an example of a module from
<nixpkgs/nixos/modules>
that declares some options to provide a session for the i3 window manager.
(Comments and formatting are my own)
# <nixpkgs/nixos/modules/services/x11/window-managers/i3.nix>
{ config, lib, pkgs, ... }: with lib; let
# it's convention to define `cfg` as your own config
cfg = config.services.xserver.windowManger.i3;
in {
options = {
services.xserver.windowManager.i3 = {
# lib.mkEnableOption generates an option that accepts a bool and has the
# description "Whether to enable {arg}"
enable = mkEnableOption "i3 window manager";
# lib.mkOption is more general and accepts a whole set of options.
# in this case, a default value, a description, and a lib.types type
configFile = mkOption {
default = null;
type = with types; nullOr path;
description = ''
Path to the i3 configuration file.
If left at the default value, $HOME/.i3/config will be used.
'';
};
extraSessionCommands = mkOption {
default = "";
type = types.lines;
description = ''
Shell commands executed just before i3 is started.
'';
}
# sometimes packages have multiple variants you can use in place of the
# original.
# in these cases, an option `package` is usually supplied to configure
# what the module puts in your environment.
package = mkOption {
type = types.package;
default = pkgs.i3;
defaultText = "pkgs.i3";
example = "pkgs.i3-gaps";
description = ''
i3 package to use.
'';
};
extraPackages = mkOption {
type = with types; listOf package;
default = with pkgs; [ dmenu i3status i3lock ];
example = literalExample ''
with pkgs; [
dmenu
i3status
i3lock
]
'';
description = ''
Extra packages to be installed system wide.
'';
};
};
};
# lib.mkIf only applies configuration if the predicate is true
config = mkIf cfg.enable {
environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
environment.etc."i3/config" = mkIf (cfg.configFile != null) {
source = cfg.configFile;
};
services.xserver.windowManager.session = [
{
name = "i3";
start = ''
${cfg.extraSessionCommands}
# lib.optionalString returns "" if the predicate is false
${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null)
"-c /etc/i3/config"
} &
waitPID=$!
'';
};
];
};
imports = [
# lib.mkRemovedOptionModule returns a path to a module with a single option
# that throws an error message
(mkRemovedOptionModule [ "services" "xserver" "windowManager" "i3-gaps" "enable" ]
"Use services.xserver.windowManager.i3.enable and set services.xserver.windowManager.i3.package to pkgs.i3-gaps to use i3-gaps.")
];
}
As you can see, it's pretty simple. If you enable it, you get the i3
package
and an X session that runs the provided binary. Like most modules, it makes
heavy use of the library provided in
<nixpkgs/lib>
.
Packages and Derivations
Like modules, packages are just expressions with a few specific qualities.
A package is a function that takes in a set of attributes (normally from
<nixpkgs>
) and outputs a
derivation.
A derivation represents a build action. Derivations are a good part of what makes Nix packages deterministic: they are defined by all the inputs used to make them, so the same package given different inputs will necessarily be identified as an entirely different derivation.
Let's take a look at a simple package, hello
.
# <nixpkgs/pkgs/applications/misc/hello/default.nix>
{ stdenv, fetchurl }: stdenv.mkDerivation rec {
pname = "hello";
version = "2.10";
src = fetchurl {
# `mirror://` is just a shorthand for one of the mirrors specified in
# <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>
url = "mirror://gnu/hello/${pname}-${version}.tar.gz";
# a hash is a *required* argument to fetchurl, in order to guarantee that
# the URL will always point to the same file
sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
};
# run the check phase, just `make check` by default
doCheck = true;
meta = with stdenv.lib; {
description = "A program that produces a familiar, friendly greeting";
longDescription = ''
GNU Hello is a program that prints "Hello, world!" when you run it.
It is fully customizable.
'';
homepage = "https://www.gnu.org/software/hello/manual/";
changelog = "https://git.savannah.gnu.org/cgit/hello.git/plain/NEWS?h=v${version}";
license = licenses.gpl3Plus;
maintainers = [ maintainers.eelco ];
platforms = platforms.all;
}
}
As you can see, the derivation here is created using stdenv.mkDerivation
.
Functions like these are wrappers around the builtin function derivation
,
which I will not be documenting here as it would be unproductive.
The attributes you can provide to mkDerivation
are numerous; they control dependencies, build phases, and other aspects of the build. They're described in full at Nixpkgs Manual: 6.3. Specifying dependencies.
The [Nixpkgs Manual] provides lots of other helpful documentation for writing Nix packages, including information on
<nixpkgs/lib>
,<nixpkgs/pkgs/stdenv>
,- and various tools for fetching sources and building derivations.
Commands
Now that we've taken a tour of some essential Nix concepts, it's time to discuss how to use its various commandline utilities.
Only short summaries of the most useful commands will be provided for reference purposes. Check the manpages for full descriptions.
Nix Commands
nix-channel
Interacts with channels (Nix's equivalent to a repository: URL to a set of Nix expressions).
--list
lists added channels,--add <url> [name]
adds a channel,--update [name...]
downloads an updated copy of added channels,--remove <name>
removes a channel,--rollback
reverts the most recent--update
.
nix-collect-garbage
Deletes all Nix store paths not currently being used.
Additionally, can delete old generations of user profiles:
{--delete-old | -d}
deletes all non-current generations,--delete-older-than <period>
deletes all generations older than the period.
nix-env
Interacts with profiles, normally user environments (sets of packages available to a given user).
To specify a user other than yourself, you can use {--profile | -p} /nix/var/nix/profiles/per-user/<username>
. To interact with the system profile
(in order to use the generations-related actions), you can even specify
/nix/var/nix/profiles/system
.
For commands accepting packages as arguments, package names are provided as
regular expressions, optionally followed by a dash and a version, e.g.
hello-2.10
. If the option -A
is provided, attribute names as in
pkgs.<name>
are used instead.
{--query | -q} <name...>
queries installed packages (pass-a
to query available but not installed packages),{--install | -i} <name...>
installs packages,{--uninstall | -e} <name...>
uninstalls packages,--set <name>
uninstalls everything and installs just the specified package,{--upgrade | -u} [name...]
upgrades packages (all installed packages if none specified),--list-generations
lists generations,{--switch-generation | -G} <generation>
switches to the specified generation,--delete-generations <generation...>
deletes generations (specify a generation, an age like "30d" to select all older generations, a count like "+5" to keep all but the most recent 5 generations, or "old" to delete all but the currently active generation),--rollback
rolls back to the previous generation.
nix-prefetch-url
Prints the hash of the file downloaded from the specified URL. Useful when writing a package with a fetchurl
input.
nix-shell
Starts an interactive shell after building the dependencies of a derivation. If
the path to a derivation is not specified, shell.nix
or default.nix
(in
order of preference) in the current directory is used instead.
# shell.nix
{ pkgs ? import <nixpkgs> {} }: pkgs.mkShell {
inputsFrom = with pkgs; [ hello ];
buildInputs = with pkgs; [ python3 ];
shellHook = ''
python
'';
}
This is an example shell.nix
that uses the special derivation function
mkShell
. If you run nix-shell
in the same directory, you will enter a shell
with python3
and all the inputs needed to build hello
, and the shell will
immediately start run the python interpreter.
Instead of specifying a derivation, you can also use nix-shell -p
to specify a
list of packages you want in the environment. If you just wanted the python
interpreter, you could use nix-shell -p python3
, or even nix-shell -p python3 --run python
to enter the interpreter automatically.
NixOS Commands
nixos-enter
,nixos-generate-config
, andnixos-install
all accept a--root <path>
argument to change the path to the NixOS installation. By default,nixos-generate-config
acts on/
, and the others act on/mnt
.
nixos-enter
Chroots into a NixOS installation.
Without a command, enters an interactive shell.
{--command | -c} <cmd>
executes a command in the shell,-- <args...>
executes a command not in the shell.
nixos-generate-config
Generates configuration.nix
if it doesn't already exist, and generates or
updates hardware-configuration.nix
.
nixos-install
Installs NixOS from an existing configuration.nix
.
--no-root-passwd
do not ask for a root password. Disables root login unless otherwise specified inconfiguration.nix
.
nixos-option
Lists the properties of the specified option.
nixos-rebuild
Builds a system from the current configuration.
The following need root:
boot
builds the new configuration and makes it the default boot target,test
builds the new configuration and modifies the currently running system to match it,switch
builds the new configuration, makes it the default boot target, and modifies the currently running system to match it.
While the following can be run as non-root:
build
builds the new configuration and makes a symlinkresult
,dry-build
shows what would be performed bybuild
orboot
but otherwise does nothing,dry-activate
shows what would be performed bytest
orswitch
but otherwise does nothing,build-vm
builds the new configuration and makes a symlinkresult
, with a scriptresult/bin/run-<hostname>-vm
which runs a qemu virtual machine for the resulting system,build-vm-with-bootloader
does the same asbuild-vm
but for testing purposes uses the normal configured bootloader inside the virtual machine.
see also NixOS Manual: Chapter 3. Changing the Configuration
nixos-version
Prints the version.