Context
I admittedly have a history of being a bit of a distro-hopper; which isn't unusual per se, but I've always been a bit "extreme" about it.
Generally, I'll switch between some flavour of
windows
and
alpine
,
ubuntu
, or
arch
every few months.
That being said, since discovering
nixos
in June 2020, I've been hard stuck on it (though I do also use
nix
and
nixos
on
macos
and
wsl
respectively).
While this post probably won't convince anyone to jump ship from their current OS, I hope it does give you a bit of insight into why I've stuck with
nixos
for so long.
Initial Difficulties
One of the first things people might run into when they look into
nix
is that
nix
refers to one of the following things (at least):
-
nix
, the package manager. -
nix-lang
, the programming language. -
nixos
, the operating system. -
nixpkgs
, the package repository. - ... probably a couple of other things too...
I'm definitely not a
nix-lang
wizard, so this post will stop short of actually talking much about the particulars of the language. I hear
this guide
is a great place to start if you're interested in this however.
In my experience, you definitely don't need to
master
nix-lang
, using it in anger seemed more than enough in my case.
I will say the language feels a bit like
json
and
haskell
had a baby; maybe with a bit of
prolog
thrown in for good measure. That's definitely doing it a disservice, but the analogy probably holds.
I'll try to talk about the parts of
nix
I actually use in a pragmatic sense. To do that, we'll need to ELI5 the package manager and operating system, before delving into my configuration :)
ELI5: Nix the Package Manager
Nix is a functional package manager that happens to work on Linux, Mac, and Windows (via WSL)! I think you can also use it on at least OpenBSD but I'm not familiar with that.
You can use
nix
to install software just like you would via
homebrew
,
winget
, or any other package manager.
A "package manager" in the UNIX sense typically refers to a tool that manages OS applications, however, in this context, it refers to a tool that manages applications, or project/code dependencies.
For example, note the difference between the following three package managers:
There are fuzzy boundaries here, but they feel distinct to me. Nix can do all of these.
brew
is used for installing OS applications (tailscale, firefox, etc).asdf
is used for installing project dependencies (database, programming language, etc).npm
is used for installing code dependencies (libraries, gems, etc.)
Basic Usage
For the most basic usage, you can rely on the following commands:
-
nix search nipkgs $package
, to find packages. -
nix-env --install $package
, to install packages. -
nix-env --uninstall $package
, to uninstall packages.
But wait, hol' up... Doing this is actually a bad idea.
Using
nix-env
to install packages is actually considered a bit of a "code smell" in the
nix
community. It side-steps a lot of the benefits of using
nix
in the first place.
Functional Package Management
Unlike most package managers,
nix
describes itself as being
"functional."
In short, traditional package managers are imperative in nature. You install a package, and it mutates the global state of your system.
Contrastingly,
nix
is
functional
in nature in that it treats package management similarly to pure functions in functional programming. There is "no global state" to mutate.
This works by storing all packages in a special
/nix/store/
directory, and having
nix
manage the
PATH
environment variable to point to the correct binaries in the
/nix/store/
directory.
This gives us a few superpowers:
- Reproducibility: If you install a package, you can be sure that the package will always be the same version, with the same dependencies, and the same configuration.
- Isolation: Packages are installed in a way that they don't interfere with each other. You can have multiple versions of the same package installed at once.
- Rollbacks: If you install a package that breaks your system, you can roll back to a previous generation of your system configuration.
With some community tooling, you can even automatically change your
PATH
to point to different versions of different packages on a project-by-project basis! This allows you to completely replace
asdf
,
rvm
,
pyenv
, etc, with the added benefit of leveraging the largest software repository in the world.
Declarative Packagamenet Management
So, functional package management gives you some advantages over plain old imperative package management, but we can do one better!
nix
also allows you to manage your packages in a
declarative
way.
This means you can define your system configuration in a file, and
nix
will ensure that your system is in the state you've defined.
Fundamentally,
nixos
is built on this idea. You define your system configuration in a file, and
nixos
will ensure that your system is in the state you've defined -- more on that soon!
You get the ability to manage your packages in a declarative way by using
nixos
, but can leverage this ability on other systems too via:
-
home-manager
for user-specific package management on
nixos
-- also usable onnix
, especially useful formacos
users. -
nix-darwin
for managing
macos
configurations and declaratively managingbrew
packages and casks. -
flakes
for managing
nix
configurations in a more modern, efficient, and user-friendly way, though still experimental at the time of writing.
A minimal example of a
nixos
style
configuration.nix
might look like this:
{ config, lib, pkgs, ... }:
{
imports = [];
environment.systemPackages = with pkgs; [ firefox steam zsh tmux ];
}
Every time you apply one of these configurations,
nix
will ensure that your system is in the state you've defined, and will make any changes necessary to get there. It stores the current state of your system in a "generation" so you can always roll back if something goes wrong.
Since everything is pure and declarative, you get the following superpowers basically for free:
- Reproducibility: You can share your configuration with others, and they can get the exact same system as you.
- Safety: You can make changes to your system configuration without fear of breaking anything. If something goes wrong, you can always roll back.
- Flexibility: You can define your system configuration in a way that makes sense to you, and reuse bits and pieces across different configurations.
ELI5: Nix the Operating System
So, as mentioned earlier,
nixos
is an operating system built on top of
nix
the package manager.
The simplest way to think about
nixos
is that it's what happens when you apply the principles of
nix
to the entire operating system, rather than solely packages: you define your system configuration in a file, and
nixos
will ensure that your system is in the state you've defined functionally & declaratively.
Where
nix
will manage your packages,
nixos
will manage your entire system configuration, including packages, services, users, and more, all via the same configuration mechanism described above.
The thing about
nixos
is that it's pretty unlike any other operating system, Linux based or otherwise, that you've probably used before.
Linux has the concept of the "filesystem hierarchy standard" (FHS) which defines where things should go on the filesystem.
nixos
doesn't really care about this standard, and instead manages literally everything via the
/nix/store/
directory, symlinked into the filesystem as needed.
That means no
/bin
, no
/lib
, etc. The main downside is that you can't just install a package from the internet (i.e.
curl | sh
) and expect it to work -- stuff generally has to be managed via
nix
to function properly.
The upside is that you get all the superpowers of
nix
applied to your entire system, and you can manage your system configuration in a declarative way, just like you would with packages, including the ability to roll back to a previous generation if something goes wrong.
You edit your system configuration, which is typically defined in
/etc/nixos/configuration.nix
, and run
nixos-rebuild switch
to tell
nix
to build all the packages and configuration it needs. Your
PATH
, current
nix-profile
generation, and everything else is then updated accordingly.
This works so well that I personally manage all mine and my family's machines with
nixos
, and I've never been happier with my setup. If I need to fix or update something for them I just push a commit to my
nix-config
repo and ask them to run
nixos-rebuild switch
on their machine.
Modules
Since
nix-lang
is used to configure your system, and because
nix-lang
is a fully-fledged, abet somewhat strange programming language, you can refactor your configuration into smaller modules.
If you really wanted to, you could even unit test and lint your configuration, though I've never felt the need to do so.
The following module is something I wrote that is part of my
configuration.nix
:
{ pkgs, lib, config, ... }:
with lib;
{
options.modules.proxy = {
enable = mkOption { type = types.bool; default = false; };
openFirewall = mkOption { type = types.bool; default = false; };
firewallPorts = mkOption { type = types.listOf types.port; default = [ 80 443 ]; };
proxies = mkOption { type = types.attrsOf types.port; default = { }; };
};
config = mkIf config.modules.proxy.enable {
networking.firewall.allowedTCPPorts = mkIf config.modules.proxy.openFirewall config.modules.proxy.firewallPorts;
security.acme.acceptTerms = true;
security.acme.defaults.email = "contact@vereis.com"
services.nginx = {
enable = true;
virtualHosts =
builtins.mapAttrs (host: port: {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://127.0.0.1:${toString port}/";
}) config.modules.proxy.proxies;
};
};
}
This module can be imported and enabled like so:
imports = [
modules/services/tailscale.nix
modules/services/proxy.nix
]
modules.tailscale.enable = true;
modules.proxy = {
enable = true;
openFirewall = true;
proxies = {
"sonarr.vereis.com" = 8989;
"radarr.vereis.com" = 7878;
"prowlarr.vereis.com" = 9696;
"transmission.vereis.com" = 9091;
"lidarr.vereis.com" = 8686;
"readarr.vereis.com" = 8787;
"printer.vereis.com" = 631;
};
};
This short module plus configuration does the following things:
-
Installs
nginx
. - Opens any specified ports in my firewall.
-
Automatically creates a
nginx
config based on the providedproxies
map to automatically setup the specified virtual hosts. -
Configures a service to automatically start and monitor it via
systemd
. - Starts the service if it isn't already started, otherwise it restarts it.
One important call out is that the
nix-lang
is lazily evaluated, and different modules ultimately have their configurations merged together.
This means multiple modules can all independently set the
networking.firewall.allowedTCPPorts
configuration, and
nix
will merge them together into a single system config; and the compiler generally validates that your config doesn't do anything strange like set the same single configuration multiple times.
This leads to modules which can utilize other modules in almost magical ways. It means that a module for
fzf
and a module for
oh-my-zsh
can both utilize the built in
zsh
module to enable shell hooks automagically, and suddenly, a
direnv
module can does the exact same thing, and you never have to manually touch your
.zshrc
to get it working.
ELI5: Nix Flakes
The weird thing about
flakes
is that they're marked an experimental feature and disabled by default.
I think this is a bit of a shame, because
flakes
are a superpower that I think everyone should be using, and they have generally been embraced by the wider
nix
community.
I didn't really use
flakes
until relatively recently, but if you're new to
nix
I think you're definitely better off doing so.
They "fix" one of the only imperative parts of
nix
that I've glossed over called
channels
.
Replacing channels
If you want to install
firefox
, say,
nix
will look up the package definition for
firefox
in
nixpkgs
.
As it happens,
nixpkgs
is the largest software repository in the world. Even larger thanarch linux
's (including theAUR
).
What actually happens under the hood is that
nix
will find the
firefox
package definition in
nixpkgs
, which is defined in your system at installation time unless you've manually changed it since.
This global state is effectively what a
nix-channel
is. You can add third party channels too, much like third party
apt
repositories. Pretty much the same concept.
These
nix-channels
are, by default, used whenever you need to interact with
nix-env
or
nixos-rebuild
. This means the exact packages you're installing are implicitly coupled to your system's state at the time of installation.
You can update channels the same way as running
sudo apt-update && apt-upgrade
by running
nix-channel --update
, but this will also update every package in your system.
The problem is, and it really is a problem: you probably aren't getting any red flags from this... because that's how practically every other OS works.
But this piece of implicit, global state directly goes against the ethos of
nix
and
nixos
in my opinion.
Simply: I lied when I said
nix
and
nixos
are "reproducible", they
basically
are, assuming your
nix-channel
is the same across rebuilds.
Thankfully,
flakes
are the solution to this!
You can add a
flake.nix
to the root of your projects, or use a
flake.nix
instead of a
configuration.nix
, which when evaluated, will create a
flake.lock
locking the exact versions of every package you've installed.
This means that you can upgrade individual packages without mutating the global state of your system, much like how
npm
package locking works.
More importantly, it means if you copy a
flake.nix
and
flake.lock
from one machine to another, you're more or less guaranteed the exact same behaviour on both machines!
Historically you could have achieved something similar with projects such as
niv
, but
flakes
are built in and are touted as the future of
nix
package management.
Developer shells
While I didn't touch on this earlier, another superpower that
nix
gives you is the ability to enter a
nix-shell
.
You can think of a
nix-shell
as being a transient, short-lived
nix-profile
instance that you can enter to get access to a specific set of packages.
If you've ever tried developing entirely in a
docker
container, you'll know how much of a pain it can be to get your editor, shell, and other tools to work correctly. The idea is
similar
to that, but no contained involved. Just a sub-shell with a different
PATH
.
You can create
nix-shell
instances on the fly via
nix-shell -p
, or you can add a
shell.nix
to any directory to declaratively specify packages and shell configuration (environment variables and the like) and run
nix-shell
.
A common workflow in the community involved using a combination of the following:
- lorri which uses a daemon to evaluate shells in the background and provides a caching mechanism.
-
direnv
which automatically enters a shell whenever you
cd
into a directory with ashell.nix
.
These tools, in tandem, basically replace your standard asdf-vm based workflow.
Using
flakes
provides a similar advantage, but without the need for
lorri
(or
direnv
if you're willing to get rid of the automatic shell entering).
For example, this blog's
flake.nix
is pretty small, but installs
sqlite
,
elixir
and
nodejs
, a few dev dependencies, and sets some env variables:
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { inherit system; };
# `sha256` can be programmatically obtained via running the following:
# `nix-prefetch-url --unpack https://github.com/elixir-lang/elixir/archive/v${version}.tar.gz`
elixir_1_15_7 = (pkgs.beam.packagesWith pkgs.erlangR26).elixir_1_15.override {
version = "1.15.7";
sha256 = "0yfp16fm8v0796f1rf1m2r0m2nmgj3qr7478483yp1x5rk4xjrz8";
};
in
with pkgs; {
devShells.default = mkShell {
buildInputs = [
# API Deps ------------------------------------
elixir_1_15_7 sqlite
# Web Deps ------------------------------------
nodePackages.prettier nodejs_20
]
++ lib.optionals stdenv.isLinux ([ libnotify inotify-tools ])
++ lib.optionals stdenv.isDarwin ([ terminal-notifier
darwin.apple_sdk.frameworks.CoreFoundation
darwin.apple_sdk.frameworks.CoreServices
]);
env = {
ERL_AFLAGS = "-kernel shell_history enabled";
};
};
}
);
}
This blog also has a
.envrc
for
direnv
that enables automatic shell entering:
#!/usr/bin/env bash
if ! has nix_direnv_version || ! nix_direnv_version 2.4.0; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.4.0/direnvrc" "sha256-XQzUAvL6pysIJnRJyR7uVpmUSZfc7LSgWQwq/4mBr1U="
fi
use flake
However, you can always run
nix develop
manually to enter the shell, or use
lorri
if you prefer.
Running Programs
I don't do this very often outside of demoing
nix
to people, but you can also run programs via
flakes
.
What I mean by that is any project on GitHub or otherwise that has a
flake.nix
can be run via
nix run
.
Again,
nix
seems to have its eyes on replacing a bunch of language specific tooling... this time we effectively have
npx
for all languages!
See the docs for nix run $package if you want more information!
My Configuration
I store my
nix-config
on GitHub, and the only prerequisite to using it is having
nix
installed, and having
git
for initial bootstrapping.
I usually do my first install via a
nix-shell -p git
, but after that, my config will bootstrap itself and from then on I'll have access to all the tools I need to rebuild in future.
Installation & Top Level
If I'm using a MacOS or Linux machine, I can jump straight into the default terminal and install
nix
.
I strongly recommend using the
Determinate Systems Nix Installer
, especially on MacOS, as it comes with a lot of niceties when compared to the vanilla
nix
installer.
On Windows 10 or 11, you can import the
NixOS WSL
image directly and get a real
nixos
experience on Windows.
One of the nice things about using
flakes
is that it lets you define various
nixosConfigurations
as a first class feature of
flakes
, which wasn't always the case.
Before
flakes
, sharing your configuration and trying to reuse modules between multiple machines had to be done manually via importing different files to a given installation's base
configuration.nix
which could admittedly be error prone.
Using
nix-darwin
on MacOS, you can define
darwinConfigurations
which are the same thing as
nixosConfigurations
, but for MacOS.
As a fun fact, all my machines named after characters in the Madoka Magica universe.
For example,
madoka
,sayaka
,kyubey
, andiroha
refer to my main workstation, XPS 15, homelab server, and MBP respectively.
I try to keep my top level
flake.nix
pretty light. It installs all the third party modules I want to use via
inputs
, and then it builds all my
nixosConfigurations
or
darwinConfigurations
as needed.
{
description = "Vereis' NixOS configuration";
inputs =
{
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
zjstatus.url = "github:dj95/zjstatus";
nix-homebrew.url = "github:zhaofengli-wip/nix-homebrew";
nix-darwin = {
url = "github:LnL7/nix-darwin";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
nixos-wsl = {
url = "github:nix-community/NixOS-WSL";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs @ { self, nixpkgs, nixos-wsl, zjstatus, home-manager, ... }:
let
username = "vereis";
in
{
darwinConfigurations = (
import ./machines/darwin { inherit (nixpkgs) lib; inherit inputs nixpkgs home-manager nix-darwin, nix-homebrew; }
)
nixosConfigurations = (
import ./machines/linux { inherit (nixpkgs) lib; inherit inputs nixpkgs home-manager nixos-wsl username zjstatus; }
);
};
}
Because I use a lot of different machines, I delegate actually defining
darwinConfigurations
and
nixosConfigurations
to configuration files in
machines/darwin/
and
machines/linux/
.
Note: you can import a directory rather than a single module via
nix
, which implicitly loadsmy/directory/default.nix
for you.
I'll focus this post on
nixosConfigurations
, but the
darwinConfigurations
are pretty similar and I never felt the need to read the darwin-specific docs to get it working.
Machine Definitions
My top-level
default.nix
contains the boilerplate for defining a specific
nixosSystem
like so:
Note: I've omitted some of the boilerplate for brevity.
{ lib, inputs, nixpkgs, nix-darwin, home-manager, nixos-wsl, zjstatus, username, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; config.allowUnfree = true; };
lib = nixpkgs.lib;
in
{
# Workstation PC
madoka = lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs username zjstatus; };
modules = [
./madoka
./configuration.nix
nixos-wsl.nixosModules.wsl
home-manager.nixosModules.home-manager {
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.extraSpecialArgs = { inherit username zjstatus; };
home-manager.users.${username}.imports = [(import ./home.nix)] ++ [(import ./madoka/home.nix)];
}
];
};
# Dell XPS 13
homura = lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs username zjstatus; };
modules = [
./homura
./configuration.nix
home-manager.nixosModules.home-manager {
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.extraSpecialArgs = { inherit username zjstatus; };
home-manager.users.${username}.imports = [(import ./home.nix)] ++ [(import ./homura/home.nix)];
}
];
};
# Server / Homelab
kyubey = lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs username zjstatus; };
modules = [
./kyubey
./configuration.nix
home-manager.nixosModules.home-manager {
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.extraSpecialArgs = { inherit username zjstatus; };
home-manager.users.${username}.imports = [(import ./home.nix)] ++ [(import ./kyubey/home.nix)];
}
];
};
...
}
I use home-manager to manage my user-specific packages and configuration. You can ignore any references to other inputs as they'll likely not be needed.
All systems import the top-level
machines/linux/configuration.nix
which is responsible for configuring packages/services which are shared between all my machines, such as ensuring packages like
killall
and
lsof
are installed,
flakes
are enabled, etc.
Additionally, all systems then import their specific
home.nix
,
services.nix
,
default.nix
and
hardware-configuration.nix
from
machines/linux/$HOSTNAME/
so that machines can be customized as needed.
Home Modules
Because I seldom use programs "raw", I generally have a
home.nix
module for each machine that imports all the other modules I need.
The benefit of using
home-manager
to manage packages is that I can easily include my dotfiles and other configuration/scripts in the same module so I get
my
preferred setup on every machine without needing to use tools like
rsync
or
chezmoi
.
Generally speaking,
home-manager
is able to install anything that you can install anything in
nixpkgs
, so 75% of my
nix
configuration can be found in any specific machine's
home.nix
, or under the reusable modules defined in
modules/home/
.
The main time you can't rely on
home-manager
to install something is if its a "service".My heuristic is if something needs a daemon or service to run, it probably can't be installed via
home-manager
.When this is the case, I'll write a module under
modules/services/
and import it in my machine'sservices.nix
instead.
You get access to a high-level DSL which wraps and exposes configuration options for any packages you might install when using
home-manager
.
You can see a list of supported options here .
And you can generally also set raw configuration if needed as an escape hatch, though I rarely do so.
My
neovim.nix
module is one of my few exceptions -- it's important to me that if needed, I can reuse my
neovim
configuration on non-
nix
managed machines, so I actually split my config between
neovim.nix
which bootstraps the package and dependencies, and
neovim/**.lua
which is my actual
neovim
configuration.
{ config, lib, pkgs, ... }:
with lib;
{
options.modules.neovim = {
enable = mkOption { type = types.bool; default = false; };
};
config = mkIf config.modules.neovim.enable {
home.packages = with pkgs; [
stylua
sumneko-lua-language-server
shellcheck
shfmt
vale
deno
nodePackages.prettier
];
programs.fzf.enable = true;
programs.fzf.enableZshIntegration = true;
home.sessionVariables = {
FZF_DEFAULT_COMMAND = "rg --files | sort -u";
EDITOR = "nvim";
VISUAL = "nvim";
};
home.file.".vale.ini".source = ./neovim/.vale.ini;
home.file.".config/nvim/lua/config.lua".source = ./neovim/config.lua;
home.file.".local/share/nvim/site/pack/packer/start/packer.nvim" = {
source = builtins.fetchGit {
url = "https://github.com/wbthomason/packer.nvim";
ref = "master";
rev = "afab89594f4f702dc3368769c95b782dbdaeaf0a";
};
};
programs.neovim = {
enable = true;
viAlias = true;
vimAlias = true;
vimdiffAlias = true;
withNodeJs = true;
withPython3 = true;
extraConfig = ''
lua require('config')
'';
};
};
}
Note that
extraConfig = '' lua require('config') '';
which is responsible for delegating to my raw config.I did originally
git clone
a seperateneovim
repo whenever I did anixos-rebuild switch
but due to the immutability ofnix
, I had to manually update cached repo SHAs as they changed...This is one case where I had to use the escape hatch, but I'm happy with the compromise.
When a machine configuration imports
modules/home/vim.nix
, and sets
modules.vim.enabled = true
in their configuration, my
vim.nix
module will:
- Install all required packages.
- Setup shell integration for all installed packages, and configures them as needed.
- Copies over any dotfiles into the required places.
Upon booting
neovim
, my config is then responsible for bootstrapping my plugin manager, which sets everything else up for me automatically.
Service Modules
As I've said: not all things can be installed via
home-manager
.
But I've also already shown an example of a "service" in my system: my
proxy.nix
module.
{ pkgs, lib, config, ... }:
with lib;
{
options.modules.proxy = {
enable = mkOption { type = types.bool; default = false; };
openFirewall = mkOption { type = types.bool; default = false; };
firewallPorts = mkOption { type = types.listOf types.port; default = [ 80 443 ]; };
proxies = mkOption { type = types.attrsOf types.port; default = { }; };
};
config = mkIf config.modules.proxy.enable {
networking.firewall.allowedTCPPorts = mkIf config.modules.proxy.openFirewall config.modules.proxy.firewallPorts;
security.acme.acceptTerms = true;
security.acme.defaults.email = "contact@vereis.com"
services.nginx = {
enable = true;
virtualHosts =
builtins.mapAttrs (host: port: {
enableACME = true;
forceSSL = true;
locations."/".proxyPass = "http://127.0.0.1:${toString port}/";
}) config.modules.proxy.proxies;
};
};
}
This module is responsible for configuring a
nginx
service, and setting up virtual hosts based on the
proxies
map.
I don't really use too many other services, the ones I do have generally are only used on my homelab server, though I do use the following on all machines:
-
tailscale.nix
which is responsible for configuring Tailscale on all my machines. -
printer.nix
which is responsible for configuring CUPs on all my machines.
What's next?
This generally sums up my personal usage of
nix
and related tools. tl;dr:
-
I use
nix
to manage my packages, my system, and my user configuration. -
I use
nixos
to manage my system configuration. -
I use
home-manager
to manage my user configuration. -
I use
flakes
to manage what versions of packages are installed, on a project-by-project or system-by-system basis.
This literally gives me superpowers -- I can confidently say I haven't distrohopped in years (outside of running Windows for games and other exclusive software).
Hopefully, this post can helped you get to grips with the admittedly very daunting
nix
tech-tree a little!
You can definitely get a subset of the benefits from using tools like
docker
,
ansible
,
asdf
,
pyenv
, etc, but the single biggest superpower for
nix
is that its all a single tool.
There's even more that I haven't touched on yet:
- You can manage remote machines via nixops .
- You can build cross-compilers for any architecture you want.
-
You can build
minimal
docker images
without installing
docker
. -
You can use
nix
as a solution to CI or testing via hydra and cachix .
There's always more to learn, but I hope this post has given you a good starting point to get to grips with
nix
and its related tools.