theme | paginate | marp |
---|---|---|
default |
true |
true |
Nix, the declarative approach
- You upgrade a package in your system and find that others packages are broken because a shared dependency got upgraded aswell...
- Your system is broken and there is no undo button...
- You want to migrate a system installation and configuration to another machine but don't want to redo all the steps you did for installing it..
- You installed a lot of dependencies during an installation but don't want to bother cleaning it all manually.
- You spend a lot of time setting up environment for development.
- You want to embrace real reproducibility.
<style scoped> section { font-size: 25px; } .gray { color: lightgray; } </style>
- Reproducible development environments.
- Easy installation of software over URLs.
- Easy transfer of development environments between machines.
- Declarative and reproducible specification of Linux machines.
- Reproducible integration testing using virtual machines.
- Avoidance of version conflicts with already installed software.
- Installing software from source code.
- Transparent build caching using binary caches.
- Strong support for software auditability.
- First-class cross compilation support.
- Remote builds.
- Remote deployments.
- Atomic upgrades and rollbacks.
- A purely functional package manager.
- Can be installed on any Linux systemd based system (Ubuntu, macOS, WSL2..)
- Has 100 000+ packages.
- Declarative.
- Atomic.
- Reproducible.
sh <(curl -L https://nixos.org/nix/install) --daemon
Once installed, one can temporarly install a package using:
nix-shell -p fastfetch
/nix/store/b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z-firefox-33.1/
Contains all the build products including binaries, libraries, configurations files... Permit the installation of multiple packages with different versions and configurations. Are immutable, isolated and atomic
my-python-app/app.py
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return {
"message": "Hello, Nix!"
}
def run():
app.run(host="0.0.0.0", port=5000)
if __name__ == "__main__":
run()
my-python-app/shell.nix
with import <nixpkgs> {};
stdenv.mkDerivation {
name = "python-environment";
buildInputs = [
pkgs.python311
pkgs.python311Packages.flask
];
shellHook = ''
export FLASK_APP="app.py"
echo "===================================="
echo "Welcome to my-python-app environment"
echo "usage: python3 app.py"
echo "===================================="
'';
}
Experimental feature of Nix (but is vastly used by the majority of the community)
Allow for a standardized project structure.
Make it easier to write, share and deploy reproducible Nix expression.
Cache the produced evaluation for faster runtime
Pin versions of dependencies in a lock file.
flake.nix
{
description = "A Python project with Nix Flakes";
inputs = {
nixpkgs.url = "nixpkgs/nixpkgs-24.05-darwin";
};
outputs = { self, nixpkgs }:
let
pkgs = nixpkgs.legacyPackages.aarch64-darwin;
in
{
# Define a development shell for the project (callable with nix develop)
devShells.${system}.default = pkgs.mkShell {
buildInputs = with pkgs;
[
# Packages needed to dev in the project
python311
...
docker-compose
# tools
jq
# Linter / formatters...
black
ruff
# git shenanigans
git
pre-commit
];
shellHook = ''
echo "Start the postgres local db"
docker compose up -d
trap 'docker compose down' EXIT
echo "Configure pre-commit to reduce circle-ci costs"
pre-commit install --hook-type pre-commit
'';
};
};
}
flake.lock
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1724615852,
"narHash": "sha256-CB8YqljFSCXwW51LKAZYIQNsKypppHfraotRSYXDU7Q=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "bb8bdb47b718645b2f198a6cf9dff98d967d0fd4",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixpkgs-24.05-darwin",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
{
description = "A flake with two channels";
inputs = {
nixpkgs.url = "nixpkgs/nixpkgs-24.05-darwin";
nixpkgs-unstable.url = "nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs, nixpkgs-unstable }:
...
pkgs.firefox
pkgs.unstable.chromium
...
}
Docker is repeatable, but not reproducible because it relies on sources that change over time (such as apt repositories)
One docker build can have different output depending when it is done.
Because of:
FROM ubuntu:latest
RUN apt update
RUN apt install nginx
Nix on the other hand is reproducible because it can pins down dependencies versions using our flake.lock
Or by overriding a package version:
{ pkgs ? import <nixpkgs> { } }:
let
version = "1.20.2";
in
pkgs.mkShell {
buildInputs = [
(pkgs.nginx.overrideAttrs (oldAttrs: {
inherit version;
src = pkgs.fetchurl {
url = "https://nginx.org/download/nginx-${version}.tar.gz";
sha256 = "sha256-lYh2dXeCGQoWU+FNwm38e6Jj3jEOBMET4R6X0b70WkI=";
};
}))
];
}
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install flask
EXPOSE 5000
ENV FLASK_APP=app.py
CMD ["python", "app.py"]
pkgs.dockerTools
is a set of functions for creating and manipulating Docker images (note that Docker is not used behind the hood to perform these functions)
{ pkgs ? import <nixpkgs> { } }:
pkgs.dockerTools.buildLayeredImage {
name = "flask-app";
config = {
Cmd = [
(pkgs.lib.getExe (pkgs.python3.withPackages (ps: with ps; [ flask ])))
./app.py
];
ExposedPorts = { "5000/tcp" = { }; };
};
}
nix-build DockerInNix.nix
docker run < result
TODO
NixOS is a immutable declarative linux distribution that focus on reproducibility using the Nix package manager
After installation, one can find the nixos configuration file at /etc/nixos/configuration.nix
{ pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
boot.loader.systemd-boot.enable = true;
networking.hostName = "JustAlternate-nixos-computer";
networking.networkmanager.enable = true;
time.timeZone = "Europe/Paris";
console.keyMap = "fr";
users.users.justalternate = {
isNormalUser = true;
extraGroups = [ "networkmanager" "wheel" ];
};
environment.systemPackages = with pkgs; [
vim
wget
];
services.openssh.enable = true;
system.stateVersion = "24.11";
}
We also have a hardware-configuration.nix
file containing our hardware auto generated config.
{ config, lib, pkgs, modulesPath, ... }:
{
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-amd" ];
boot.extraModulePackages = [ ];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/eba54cc4-e684-474c-a38b-d3033e5e657b";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/E329-BF6B";
fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ];
};
swapDevices =
[ { device = "/dev/disk/by-uuid/5f0732ca-9d43-432f-8bf9-bad19a61a596"; }
];
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}
Now lets install application and services on our NixOS machine
...
<style scoped> section { font-size: 25px; } .blue { color: blue; } </style>
- yarn2nix: Generate Nix expressions from a yarn.lock file
- node2nix: Generate Nix expression from a package.json.
- poetry2nix: Build Python packages directly from Poetry's poetry.lock. No conversion step needed.
- compose2nix: Generate a NixOS config from a Docker Compose project (only for NixOS)
- composer2nix: Generate Nix expressions to build composer packages.
- sbtderivation: mkDerivation for sbt, similar to buildGoModule.
- nixos-infect: Replace a running non-NixOS Linux host with NixOS.
TODO
TODO