NixOS

NixCon 2020. Talks. Discussions. Hacking.
October 16th-18th 2020
  • 00days
  • 00hours
  • 00minutes
  • 00seconds

Reproducible builds and deployments.

Nix

A powerful package manager for Linux and other Unix systems that makes package management reliable and reproducible. Share your development and build environments across different machines.

NixOS

A Linux distribution with a unique approach to package and configuration management. Built on top of the Nix package manager, it is completely declarative, makes upgrading systems reliable, and has many other advantages.

$ # Hi!$ # Wondering how to start using Nix?$ # Here are a few examples:$ node -e "console.log(1+1)"node: command not found$ # Interesting, no node on this machine$ # No problem with Nix!$ nix-shell -p nodejs(nix-shell) $ node -e "console.log(1+1)"2(nix-shell) $ # And now we are able to use node.(nix-shell) $ # Nix Magic! :)$ # Typing "nix-shell -p ..." each time can be tedious. We can do better.$ # We can write everything down in shell.nix$ cat -n shell.nix 1 { pkgs ? import <nixpkgs> {} # here we import the nixpkgs package set 2 }: 3 pkgs.mkShell { # mkShell is a helper function 4 name="dev-environment"; # that requires a name 5 buildInputs = [ # and a list of packages 6 pkgs.nodejs 7 ]; 8 shellHook = '' # bash to run when you enter the shell 9 echo "Start developing..." 10 ''; 11 }$ # Pause the video to understand the shell.nix$ # To enter dev-environment simply run:$ nix-shellStart developing...(nix-shell) $ node -e "console.log(1+1)"2(nix-shell) $ # Now go ahead commit shell.nix to your repository(nix-shell) $ # and share your development environment with your coworkers$ # For the last example, let us build a minimal docker image with Nix$ cat -n docker-redis.nix 1 { pkgs ? import <nixpkgs> { system = "x86_64-linux";} 2 }: # nixpkgs package set 3 pkgs.dockerTools.buildLayeredImage { # helper to build docker image 4 name = "nix-redis"; # give docker image a name 5 tag = "latest"; # provide a tag 6 contents = [ pkgs.redis ]; # packages in docker image 7 }$ # Pause the video and take the time to understand docker-redis.nix file$ # Now let's build the docker image and load it into docker$ nix-build docker-redis.nix -o ./result... SKIPPING OUTPUT .../nix/store/1crapx24sjgqm2j1wmq17k6f6a9wy66d-docker-image-nix-redis.tar.gz$ docker load -i ./resultLoaded image: nix-redis:latest$ docker images | grep redisdebian-redis latest 8366943c77e8 3 days ago 136MBalpine-redis latest aae644cd3417 3 days ago 6.99MBnix-redis latest 30486183a209 50 years ago 45.4MB$ # The size of our docker image is somewhere between a Debian and$ # an Alpine image$ # The redis packaged in nixpkgs is not optimized for small size$ # Let us fix this!$ cat -n redis-minimal.nix 1 { pkgs ? import <nixpkgs> {} 2 }: 3 pkgs.redis.overrideAttrs (old: { 4 # no need for systemd support in our docker image 5 makeFlags = old.makeFlags ++ ["USE_SYSTEMD=no"]; 6 # build static binary with musl 7 preBuild = '' 8 makeFlagsArray=(PREFIX="$out" 9 CC="${pkgs.musl.dev}/bin/musl-gcc -static" 10 CFLAGS="-I${pkgs.musl.dev}/include" 11 LDFLAGS="-L${pkgs.musl.dev}/lib") 12 ''; 13 # Let's remove some binaries which we don't need 14 postInstall = "rm -f $out/bin/redis-{benchmark,check-*,cli}"; 15 })$ # In redis-minimal.nix we override the default redis build with three changes:$ # 1.) Remove the redis systemd support$ # 2.) Build a statically linked binary with musl$ # 3.) Remove all binaries apart from redis-server$ # Now let's build the docker image with our newly created minimal redis$ cat -n docker-redis-minimal.nix 1 { pkgs ? import <nixpkgs> { system = "x86_64-linux";} 2 }: # nixpkgs package set 3 let 4 redisMinimal = import ./redis-minimal.nix { inherit pkgs; }; 5 in 6 pkgs.dockerTools.buildLayeredImage { # helper to build docker image 7 name = "nix-redis-minimal"; # give docker image a name 8 tag = "latest"; # provide a tag 9 contents = [ redisMinimal ]; # use redisMinimal package 10 }$ # Let's build the new docker image now$ nix-build docker-redis-minimal.nix -o ./result ... SKIPPING OUTPUT .../nix/store/83zcgs5xvzrgx09iv8s82wkabl8xkr03-docker-image-nix-redis-minimal.tar.gz$ docker load -i ./resultLoaded image: nix-redis-minimal:latest$ docker images | grep redisdebian-redis latest 8366943c77e8 3 days ago 136MBalpine-redis latest aae644cd3417 3 days ago 6.99MBnix-redis latest 30486183a209 50 years ago 45.4MBnix-redis-minimal latest a21238890680 50 years ago 2.02MB$ # Did we just produce a docker image _smaller_ than Alpine? Interesting!$ # Go tell your friends :)$ # This was a quick taste of what Nix can do.$ # I hope we made you eager to try it for yourself.$ # Happy Nixing!
  • Reproducible

    Nix builds packages in isolation from each other. This ensures that they are reproducible and don't have undeclared dependencies, so if a package works on one machine, it will also work on another.

  • Declarative

    Nix makes it trivial to share development and build environments for your projects, regardless of what programming languages and tools you’re using.

  • Reliable

    Nix ensures that installing or upgrading one package cannot break other packages. It allows you to roll back to previous versions, and ensures that no package is in an inconsistent state during an upgrade.

Examples

  • Try new tools without fear

    $ # Lets see if python is present on the system$ python --versionpython: command not found$ # Use nix-shell to create a shell environment with python$ nix-shell -p python3(nix-shell) $ python --versionPython 3.7.7(nix-shell) $ # YAAAY! Python is available(nix-shell) $ exit$ # And this is how you create on demand environments
  • Multiple languages, one tool

    $ # Lets create an environment with multiple packages$ nix-shell -p python3 nodejs go rustc(nix-shell) $ node --versionv10.20.1(nix-shell) $ go versiongo version go1.14.1 linux/amd64(nix-shell) $ rustc --versionrustc 1.42.0(nix-shell) $ # Imagine how easy(nix-shell) $ exit$ # And POOF, just like that you are back to your normal environment after$ # playing around. No system was hurt during this time :)
  • Declarative development environments

    $ # You can also persist your development environment.$ # Here is a short example with python and nodejs:$ cat -n shell.nix 1 { pkgs ? import <nixpkgs> {} # here we import the nixpkgs package set 2 }: 3 pkgs.mkShell { # mkShell is a helper function 4 name="dev-environment"; # that requires a name 5 buildInputs = [ # and a list of packages 6 pkgs.python3 7 pkgs.python3Packages.virtualenv 8 pkgs.nodejs 9 pkgs.yarn 10 ]; 11 shellHook = '' # bash to run when you enter the shell 12 echo "Start developing..." 13 ''; 14 }$ # Pause the video to read and understand the shell.nix$ # To enter the dev-environment simply run:$ nix-shellStart developing...(nix-shell) $ python --versionPython 3.7.7(nix-shell) $ virtualenv --version16.7.9(nix-shell) $ # With python and virtualenv you should be ready to start(nix-shell) $ # your python project(nix-shell) $ node --versionv10.20.1(nix-shell) $ yarn --version1.22.4(nix-shell) $ # Having node and yarn in PATH you already know you can(nix-shell) $ # do all the good stuff with nodejs(nix-shell) $ exit$ # How hard is it in your company to share the same version of required$ # tooling across different machines?
  • Minimal docker images

    $ # We all love docker. But over time it can become tedious to write$ # reliable docker files.$ # What if you could use the power of Nix to build Docker images?$ cat -n docker.nix 1 { pkgs ? import <nixpkgs> { system = "x86_64-linux";} 2 }: # nixpkgs package set 3 pkgs.dockerTools.buildLayeredImage { # helper to build Docker image 4 name = "nix-hello"; # give docker image a name 5 tag = "latest"; # provide a tag 6 contents = [ pkgs.hello ]; # packages in docker image 7 }$ # Pause the video to read and understand the docker.nix$ # Now we build a Docker image with nix-build$ nix-build docker.nix -o result/nix/store/91ry9y0686xn9dgnn6rawfvknj8582ws-nix-hello.tar.gz$ # We can import the image into Docker as usual$ docker load -i ./resulte25615ae850b: Loading layer 1.649MB/1.649MBbde5792b3b71: Loading layer 256kB/256kB1d9c7edd824b: Loading layer 31.63MB/31.63MBab8ee9b997a1: Loading layer 266.2kB/266.2kBf568d8025dd8: Loading layer 71.68kB/71.68kBLoaded image: nix-hello:latest$ # You can see that the Docker layers were automatically calculated.$ docker images | grep nix-hellonix-hello latest 83667617cdb9 50 years ago 32.9MB$ # And for the final thing lets test that it really works$ docker run -ti nix-hello:latest helloHello World!$ # There is a lot we didn't cover in this little demo, but we hope$ # it shows that declarative docker images are possible.
  • Declarative cloud images

    $ # How hard would it be to build and configure an Amazon EC2 image?$ # Let us configure a Nginx to serve a "Welcome to nginx!" page, with a$ # valid SSL certificate (via LetsEncrypt) and recommended security settings$ cat -n amazon.nix 1 { pkgs, ...}: 2 { 3 security.acme.acceptTerms = true; 4 security.acme.email = "nix@example.com"; 5 services.nginx = { 6 enable = true; 7 recommendedGzipSettings = true; 8 recommendedOptimisation = true; 9 recommendedProxySettings = true; 10 recommendedTlsSettings = true; 11 virtualHosts."example.com" = { 12 enableACME = true; 13 forceSSL = true; 14 locations."/".root = "${pkgs.nginx}/html"; 15 }; 16 }; 17 }$ # Pause the video to understand the default.nix$ # Now we just need to build it.$ nix-build '<nixpkgs/nixos/release.nix>' \ -A amazonImage.x86_64-linux \ --arg configuration ./amazon.nix \ -o ./result...$ ls ./result/nixos-amazon-image-20.09pre130979.gfedcba-x86_64-linux.vhdnix-support$ # The resulting Virtual Hard Disk image (.vhd suffix) can then be$ # imported to Amazon EC2 as usual.
  • Test your configurations

    $ # In this example we will look into how to test your NixOS configuration$ # We will create a simple configuration with the `hello` package installed$ # system wide and check that the `hello` world binary works.$ cat -n test.nix 1 { pkgs ? import <nixpkgs> {} 2 }: 3 pkgs.nixosTest { 4 name = "example-test"; 5 # virtual machine with one package installed system wide 6 machine = { pkgs, ... }: { 7 environment.systemPackages = [ pkgs.hello ]; 8 }; 9 testScript = '' 10 # run hello on machine and check for output 11 machine.succeed('hello | grep "Hello, world!"') 12 # test is a simple python script 13 ''; 14 }$ # Pause the video to understand the test.nix$ # To run the test you simply need to run:$ nix-build test.nix...machine1: must succeed: hello | grep "Hello, world!".../nix/store/99cb3pmmyaxp0rs6r596kqq5v8ivp45j-vm-test-run-example-test$ # While this example is simple you can imagine how handy it can be$ # to test interactions between many machines.$ # You can take screenshots, scrape for text and more. Checkout what$ # else you can do in the Tests section in the NixOS manual$ # Happy testing!