I just migrated my server to NixOS. While not sure if it is ready for serious production, I agree on their philosophy and really like its direction. Here is my nix configurations.
NixOS is an operating system that manage packages and configurations by Nix, the package manager. It introduces itself as “The Purely Functional Package Manager”, but I would rather call it “An Immutable Package Manager” for easier understanding.
The basic idea is: For the same input, you should always get the same immutable output. If it works now, it always works.
By having no side effect, it is possible have multiple versions of the same package running on the same OS, they will have their own dependency sorted out without breaking anything. This also makes downgrading super easy, if you upgraded something and find it doesn’t work, nix-env --rollback
will roll you back to the previous version, nothing breaks. It is more amazining when upgrading NixOS, because the entire OS is built and managed by Nix, downgrading the whole OS is just 1 command nixos-rebuild switch --rollback
, that includes everything from third party softwares to kernel modules, how safe!
The reproducibility does not come for free, you have to follow NixOS’s way to configure your packages. For example if you are customising vim using ~/.vimrc
, Nix is not going to manage that file, changing that file will make vim behave differently, even the package itself is not changed. The solution would be using Nix to manage configurations.
Every package is built from a derivation, but instead of only providing derivations, Nix gives you functions that return derivations, allowing you to customise packages by passing arguments to those functions. Here is an example of customising vim, written in Nix-the-langauge.
nixpkgs.vim_configurable.customize {
name = "vim";
vimrcConfig.customRC = ''
set nocompatible
set backspace=indent,eol,start
'';
}
The code above is a Nix expression calling the nixpkgs.vim_configurable.customize
function, passing in 2 arguments, name
and vimrcConfig.customRC
. You can read more about the language from the manual, it is a dynamic, lazily evaluated, functional language. This will build a package with a script called “vim”:
#!/bin/sh
exec /nix/store/q6yhcy7g8107x9pdf8mi8fp0cf7rin33-vim_configurable-7.4.826/bin/vim -u /nix/store/ab3ghyb857y33zvngkz3i0rrmji17hrx-vimrc "$@"
As you may have guessed /nix/store/<hash>-vim_configurable-7.4.826/bin/vim
is the original vim, and /nix/store/<hash>-vimrc
is a text file that contains the vimrc that I passed to the function. By running the vim
command, you are not running the original vim, but a shell script that injects flags to vim. Configuring other packages are very much the same, look at the Nix expressions, find a useful argument, then pass things into the function.
As you can see, there is a very explicit dependency (that absolute path with hash) between the packages, Nix can figure out the dependency tree by looking at the files, and therefore can provide handle functions like nix-collect-garbage
, removing packages that no one uses.
It is also fun to use Nix as development environment. Many languages I used has some kind of “version management” or “virtual environment”, may it be nvm, rvm, python’s virtual environment, or cabal sandbox. The goal is to make sure the environment for the project to be the same, unfortunately none them covers every corner you need. Let’s say you want to use imagemagic with nodejs, you will have to run this:
brew install imagemagick
brew install graphicsmagick
npm install gm
Homebrew does not manage nodejs packages, npm does not manage system packages, so you have to handle them yourself. Here’s where Nix shines, Nix does both! You can specific your system dependency and nodejs dependency at the same place, and running nix-shell
should get you into an environment that is ready to build. I also met someone doing nodejs, python, clojure at a brilliant meetup, he uses Nix just for development!
It worths mentioning that Stack has Nix integration build in, which means you should be able to manage your Haskell project’s system dependency with Stack in a flawless way.
Instead of just managing system packages, Nix is also capable of managing project dependencies, here it overlaps those language specific package managers like cabal and pip. The way it does it, is adding those packages into Nix’s channel, for example you can find haskellPackages.tagsoup
and python3.3-pyramid
available. The repository is not very complete and you may need to contribute if you find something you need is not there.
There is another way to handle this, is by converting existing dependencies into Nix expressions. I setup my blog which is running on nodejs, by converting package.json
to some Nix expressions, using npm2nix. The generated expression is pretty straightforward, it is just putting each dependency into its own Nix package, then download them into the node_modules
folder. After reading how others setup their ghost blog, I am able to get mine running too!
I am surprised there are some tools converting different languages’ package to nix expression: erlang / elixir, go, python.
Nix does not replace Docker, but it solves similar issues with a better solution. You can get deterministic build, easy deployment, neat dependency tree with Nix, no need to download an OS just for one process, configuration works pretty much the same too (except you have to learn Nix-the-language).
However Nix does not provide the isolation Docker has. A process is just like every other ordinary process that can read and write files with the given user permission, there is no sandbox, ports bind to system directly so you cannot hide them (well, not exposing them) like you can in Docker.
I think Nix is trying to solve a problem at a fundamental level, instead of avoiding the problem by putting things into containers like Docker. However, this requires a more careful design for build process, lots of package does not follow Nix’s philosophy, therefore requires patching before / after build, that patch can be dirty.
Is it really pure? No. You can get around easily by running curl some-website | sh
in the build script. There is no 100% enforcement to pureness, people try their best to maintain pureness by code review / impure path detections / etc.
Nix (the language) also make it more difficult to start with. On other systems, you just need to run a command then something will be installed somewhere, you have no clue what is happening, but it kind of works, configuration is not centralised nor managed, but at least it works. On Nix, you need to understand the language before customising anything, learning a functional programming language can be hard, especially when Nix is not popular. Then you have to explore the nixpkgs
repo to look for useful functions (and its arguments), which is definitely not something you would do with a traditional package manager. At least I never tried to understand what apt-get
is actually doing.
mix
, npm
and stack
looks for that.wrapProgram
script from the makeWrapper
package, I wish I found that earlier.RELEASE_MUTABLE_DIR
flag, and change the path. buildStackProject
function, it makes building Haskell project much easier-K
flag will retains the build directory in the tmp folder, and it retains all dependency. I used it for debug purpose.boot.cleanTmpDir = false
by default, which means storage fill up pretty fast if I keep developing with the -K
flag.client_session_key.aes
in the current directory, which of course fails in Nix’s immutable path.
grunt
for Ghost trigger git submodules operation, which is not pure.
eth0
, and the other is enp0s4
, I thought NixOS should make sure everything remains the sameroc-enabled
ExecStart
ALL_CAPITAL
Here are some useful links I find it useful when dealing with NixOS, hopefully help someone