Aller au contenu

Langage Nix par la pratique

Mise à jour :

Dans ce guide, j’adopte une approche différente : plutôt que de lister la syntaxe de façon abstraite, nous allons construire ensemble un projet concret du début à la fin. À chaque étape, j’introduis un nouveau concept du langage Nix que nous appliquons immédiatement.

Nous allons créer un environnement de développement Python complet avec :

  • Python 3.12 avec des bibliothèques spécifiques
  • Des outils de développement (git, vim, ripgrep)
  • Des variables d’environnement personnalisées
  • Une configuration modulaire et réutilisable

À la fin de ce guide, vous aurez un shell.nix fonctionnel et vous comprendrez chaque ligne.

Prérequis

Qu’est-ce qu’un fichier shell.nix ?

Un fichier shell.nix est une recette déclarative qui décrit un environnement de développement. Quand vous lancez nix-shell dans un dossier contenant ce fichier, Nix :

  1. Lit le fichier shell.nix
  2. Télécharge les paquets nécessaires (s’ils ne sont pas en cache)
  3. Crée un shell isolé avec exactement ces outils disponibles
  4. Vous place dans cet environnement temporaire

Pourquoi utiliser shell.nix ?

Problème classiqueSolution avec shell.nix
”Ça marche sur ma machine”Environnement identique pour tous
Conflits de versions entre projetsChaque projet a son propre environnement
Installation manuelle d’outilsUn seul nix-shell suffit
Documentation des dépendancesLe fichier shell.nix est la documentation

Différence avec les environnements virtuels Python

Contrairement à venv qui ne gère que Python, shell.nix peut inclure n’importe quel outil : compilateurs, bases de données, outils CLI, etc. Et contrairement à Docker, il n’y a pas de conteneur : les outils sont directement accessibles dans votre terminal.

Étape 1 : Premier shell.nix minimal

Créons notre premier fichier dans un nouveau dossier :

Terminal window
mkdir mon-projet-python && cd mon-projet-python

Créez un fichier shell.nix avec ce contenu :

shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = [ pkgs.python312 ];
}

Testons immédiatement :

Terminal window
nix-shell
python3 --version # Python 3.12.x
exit # Sortir du shell

Ça fonctionne ! Mais qu’avons-nous écrit exactement ? Décortiquons chaque élément dans les sections suivantes.

Étape 2 : Comprendre les fonctions

Rappel de notre code

Regardons à nouveau notre shell.nix :

{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = [ pkgs.python312 ];
}

La première ligne { pkgs ? import <nixpkgs> {} }: est mystérieuse. C’est en fait une fonction. Pourquoi ? Parce que Nix a besoin de savoir où trouver les paquets comme python312. On lui dit : “donne-moi pkgs et je te construis un environnement”.

Les fonctions en Nix : la base

En Nix, une fonction s’écrit avec : qui sépare l’argument du corps :

argument: ce_que_la_fonction_retourne

Exemple concret - une fonction qui double un nombre :

x: x * 2
  • x est l’argument (le paramètre d’entrée)
  • x * 2 est le corps (ce qui est calculé et retourné)

Pour appeler cette fonction, on lui passe une valeur :

Terminal window
nix repl
nix-repl> double = x: x * 2
nix-repl> double 5
10
nix-repl> double 100
200

Fonctions avec plusieurs arguments

Nix ne supporte qu’un argument par fonction, mais on peut les chaîner :

a: b: a + b

Cela signifie : “prends a, puis retourne une fonction qui prend b et retourne a + b”.

Terminal window
nix-repl> add = a: b: a + b
nix-repl> add 3 4
7

Les arguments nommés (notre cas)

Notre shell.nix utilise une syntaxe avec accolades { } :

{ pkgs }:

Cela signifie : “j’attends un dictionnaire (attrset) qui contient une clé pkgs”. C’est plus lisible quand il y a plusieurs arguments :

{ nom, age, ville }:
"Je m'appelle ${nom}, j'ai ${toString age} ans, j'habite à ${ville}"

La valeur par défaut avec ?

Le ? définit une valeur utilisée si l’argument n’est pas fourni :

{ pkgs ? import <nixpkgs> {} }:

Traduction : “Si on me donne pkgs, je l’utilise. Sinon, je charge moi-même nixpkgs avec import <nixpkgs> {}.”

Pourquoi notre shell.nix est une fonction ?

Quand vous tapez nix-shell, voici ce qui se passe :

  1. Nix lit le fichier shell.nix
  2. Il détecte que c’est une fonction (à cause du :)
  3. Il l’appelle avec {} (un dictionnaire vide)
  4. Comme pkgs n’est pas fourni, la valeur par défaut import <nixpkgs> {} est utilisée
  5. La fonction retourne le résultat de pkgs.mkShell { ... }

C’est pourquoi cette première ligne est standard dans presque tous les fichiers Nix. Elle dit simplement : “charge les paquets disponibles”.

Étape 3 : Comprendre import et nixpkgs

Qu’est-ce que import ?

import charge et évalue un fichier Nix. C’est comme un require ou include dans d’autres langages :

# Si config.nix contient { port = 8080; }
let
config = import ./config.nix;
in
config.port # 8080

Qu’est-ce que <nixpkgs> ?

<nixpkgs> est un chemin spécial qui pointe vers la collection de paquets Nix installée sur votre système (via les channels). C’est un raccourci pour accéder aux ~80 000 paquets disponibles.

import <nixpkgs> {}

Cette expression :

  1. Charge le fichier default.nix de nixpkgs
  2. L’appelle avec {} (options vides)
  3. Retourne un attrset gigantesque contenant tous les paquets

C’est pourquoi ensuite, on peut écrire pkgs.python312, pkgs.git, etc.

Testons dans le REPL

Le REPL (Read-Eval-Print Loop) est un terminal interactif qui permet de tester des expressions Nix à la volée. Vous tapez une expression, Nix l’évalue et affiche le résultat. C’est idéal pour expérimenter :

Terminal window
nix repl
nix-repl> pkgs = import <nixpkgs> {}
nix-repl> pkgs.python312
«derivation /nix/store/...-python3-3.12...»
nix-repl> pkgs.git
«derivation /nix/store/...-git-...»

Le texte «derivation ...» signifie que c’est un paquet Nix prêt à être construit ou utilisé. Tapez :q pour quitter le REPL.

Étape 4 : Comprendre les attrsets

Structure fondamentale

Un attrset (attribute set) est un dictionnaire clé-valeur. C’est la structure de données la plus importante en Nix :

{
name = "Alice";
age = 30;
active = true;
}

On accède aux valeurs avec un point :

nix repl
nix-repl> personne = { name = "Alice"; age = 30; active = true; }
nix-repl> personne.name
"Alice"
nix-repl> personne.age
30
nix-repl> personne.active
true
:q

Notre mkShell est un attrset

Regardons à nouveau notre code :

pkgs.mkShell {
packages = [ pkgs.python312 ];
}

pkgs.mkShell est une fonction qui attend un attrset en argument. Nous lui passons { packages = [ pkgs.python312 ]; }.

Étape 5 : Ajouter plus de paquets

Enrichissons notre environnement avec git, vim et ripgrep :

shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = [
pkgs.python312
pkgs.git
pkgs.vim
pkgs.ripgrep
];
}

Rechargez l’environnement :

Terminal window
nix-shell
git --version # Disponible !
vim --version # Disponible !
rg --version # Disponible !

Simplifier avec with

Répéter pkgs. devient fastidieux. Le mot-clé with importe tous les attributs d’un attrset dans le scope :

shell.nix (amélioré)
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs; [
python312
git
vim
ripgrep
];
}

C’est équivalent, mais plus lisible. with pkgs; signifie “dans la suite, cherche d’abord dans pkgs”.

Étape 6 : Variables locales avec let

Pourquoi des variables ?

Imaginons que nous voulions définir le nom du projet et sa version à un seul endroit. Le bloc let ... in crée des variables locales :

shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
projectName = "mon-api";
pythonVersion = pkgs.python312;
in
pkgs.mkShell {
name = projectName;
packages = with pkgs; [
pythonVersion
git
vim
];
}

Syntaxe de let

let
variable1 = valeur1;
variable2 = valeur2;
in
expression_qui_utilise_les_variables

Variables qui dépendent d’autres variables

Les variables peuvent référencer celles définies avant :

let
a = 1;
b = 2;
sum = a + b; # 3
in
sum * 2 # 6

Étape 7 : Listes de paquets Python

Le problème

Nous voulons Python avec des bibliothèques comme requests, flask ou pytest. Mais pkgs.python312 est Python “nu” sans bibliothèques.

La solution : withPackages

nixpkgs fournit une fonction python312.withPackages qui crée un Python avec les bibliothèques demandées :

shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
# Python avec bibliothèques
pythonEnv = pkgs.python312.withPackages (ps: [
ps.requests
ps.flask
ps.pytest
]);
in
pkgs.mkShell {
packages = with pkgs; [
pythonEnv
git
vim
ripgrep
];
}

Testons :

Terminal window
nix-shell
python3 -c "import requests; print(requests.__version__)"
# 2.31.0 (ou version similaire)

Comprendre ps: […]

ps: [ ps.requests ps.flask ] est une fonction anonyme (lambda) :

  • ps est l’argument (les paquets Python disponibles)
  • [ ps.requests ps.flask ] est le corps (la liste retournée)

C’est équivalent à écrire :

(ps: [ ps.requests ps.flask ps.pytest ])

Cette fonction est appelée par withPackages qui lui passe tous les paquets Python disponibles.

Étape 8 : Variables d’environnement

shellHook pour le démarrage

shellHook est un script shell exécuté à l’entrée dans nix-shell :

shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
pythonEnv = pkgs.python312.withPackages (ps: [
ps.requests
ps.flask
ps.pytest
]);
in
pkgs.mkShell {
packages = with pkgs; [
pythonEnv
git
vim
ripgrep
];
shellHook = "echo '🐍 Environnement Python activé !' && echo \"Python: $(python3 --version)\"";
}

Chaînes multilignes

En Nix, les chaînes multilignes utilisent deux apostrophes simples consécutives. Voici l’équivalent avec une string standard :

# String simple avec \n
"Première ligne\nDeuxième ligne"
# En shellHook, combinez plusieurs echo :
shellHook = "echo 'Ligne 1' && echo 'Ligne 2'";

L’indentation minimale commune est automatiquement supprimée dans les chaînes multilignes Nix.

Variables d’environnement personnalisées

Ajoutez des variables avec les attributs de l’attrset :

shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
pythonEnv = pkgs.python312.withPackages (ps: [
ps.requests
ps.flask
ps.pytest
]);
in
pkgs.mkShell {
packages = with pkgs; [
pythonEnv
git
vim
ripgrep
];
# Variables d'environnement
PROJECT_NAME = "mon-api";
FLASK_ENV = "development";
FLASK_DEBUG = "1";
shellHook = "echo '🐍 Projet:' $PROJECT_NAME && echo 'Flask: mode' $FLASK_ENV";
}

Testons :

Terminal window
nix-shell
echo $PROJECT_NAME # mon-api
echo $FLASK_ENV # development

Étape 9 : Interpolation de chaînes

Syntaxe ${…}

L’interpolation permet d’insérer des expressions dans des chaînes :

let
name = "Alice";
in
"Bonjour, ${name} !"
# "Bonjour, Alice !"

Conversion de types

Seules les chaînes peuvent être interpolées. Pour les nombres, utilisez toString :

let
port = 8080;
in
"Serveur sur le port ${toString port}"
# "Serveur sur le port 8080"

Application à notre projet

shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
projectName = "mon-api";
projectVersion = "1.0.0";
pythonEnv = pkgs.python312.withPackages (ps: [
ps.requests
ps.flask
]);
in
pkgs.mkShell {
name = "${projectName}-dev";
packages = with pkgs; [
pythonEnv
git
];
PROJECT_NAME = projectName;
PROJECT_VERSION = projectVersion;
shellHook = "echo '📦' ${projectName} v${projectVersion} && echo \"Python: $(python3 --version)\"";
}

Étape 10 : Figer les versions (reproductibilité)

Le problème avec <nixpkgs>

Jusqu’ici, nous utilisons import <nixpkgs> {}. Le problème ? <nixpkgs> pointe vers le channel système qui change à chaque mise à jour. Aujourd’hui vous avez Python 3.12.3, demain peut-être 3.12.4.

Pour un environnement vraiment reproductible, il faut figer la version de nixpkgs.

Solution 1 : Figer avec fetchTarball

shell.nix
{ pkgs ? import (fetchTarball {
# nixpkgs 24.05 - figé à ce commit
url = "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz";
sha256 = "sha256:1lr1h35prqkd1mkmzriwlpvxcb34kmhc9dnr48gkm8hh089hifmx";
}) {}
}:
pkgs.mkShell {
packages = [ pkgs.python312 ];
}

Maintenant, peu importe quand ou où vous lancez nix-shell, vous aurez exactement la même version de Python.

Comment trouver le sha256 ?

Deux méthodes :

Méthode 1 : Laissez Nix le calculer (mettez une valeur bidon) :

sha256 = ""; # Nix affichera le bon hash dans l'erreur

Méthode 2 : Utilisez nix-prefetch-url :

Terminal window
nix-prefetch-url --unpack https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz

Solution 2 : Figer avec un commit précis

Pour encore plus de précision, utilisez un commit spécifique :

shell.nix
{ pkgs ? import (fetchTarball {
url = "https://github.com/NixOS/nixpkgs/archive/5ef6c425980847c78a80d759abc476e941a9bf42.tar.gz";
sha256 = "sha256:0123456789abcdef...";
}) {}
}:
pkgs.mkShell {
packages = [ pkgs.python312 ];
}

Quelle version d’un paquet ?

Pour voir quelle version d’un paquet est disponible :

Terminal window
# Dans le REPL
nix repl
nix-repl> pkgs = import <nixpkgs> {}
nix-repl> pkgs.python312.version
"3.12.12"
nix-repl> pkgs.nodejs.version
"22.20.0"

Trouver un paquet avec une version spécifique

Le site Nixhub.io permet de rechercher quelle version de nixpkgs contient une version précise d’un paquet.

Par exemple, si vous avez besoin de Python 3.11.4 exactement, Nixhub vous indiquera quel commit de nixpkgs utiliser.

Limites de cette approche

Cette méthode fonctionne, mais elle a des inconvénients :

  • Pas de fichier de verrouillage partageable facilement
  • Mise à jour manuelle des hashes
  • Pas de gestion des dépendances entre projets

C’est pourquoi les flakes ont été créés : ils résolvent ces problèmes avec un fichier flake.lock automatique. Un guide dédié aux flakes sera bientôt disponible.

Syntaxe if-then-else

En Nix, if est une expression qui retourne une valeur :

if condition then valeur1 else valeur2

Cas pratique : paquets conditionnels

Ajoutons des outils de debug uniquement en mode développement. Désormais, nous utilisons une version figée de nixpkgs :

shell.nix
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") {} }:
let
debugMode = true; # Changez à false pour production
pythonEnv = pkgs.python312.withPackages (ps: [
ps.requests
ps.flask
]);
# Outils de debug conditionnels
debugTools = if debugMode then [
pkgs.htop
pkgs.ncdu
] else [];
in
pkgs.mkShell {
packages = with pkgs; [
pythonEnv
git
] ++ debugTools; # ++ concatène les listes
shellHook =
(if debugMode then "echo '🔧 Mode DEBUG activé'\n" else "");
}

L’opérateur ++ (concaténation)

++ concatène deux listes :

[ 1 2 ] ++ [ 3 4 ]
# [ 1 2 3 4 ]

Étape 12 : Modularisation avec import

Pourquoi modulariser ?

Notre shell.nix grandit. Séparons la configuration dans des fichiers distincts.

Structure du projet

mon-projet-python/
├── shell.nix # Point d'entrée
├── python-env.nix # Configuration Python
└── config.nix # Variables de configuration

config.nix : les variables

config.nix
{
projectName = "mon-api";
projectVersion = "1.0.0";
debugMode = true;
flaskPort = 5000;
}

python-env.nix : l’environnement Python

python-env.nix
{ pkgs }:
pkgs.python312.withPackages (ps: [
ps.requests
ps.flask
ps.pytest
ps.black
ps.mypy
])

Ce fichier est une fonction qui attend pkgs en argument.

shell.nix : assemblage

shell.nix
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") {} }:
let
# Import des modules
config = import ./config.nix;
pythonEnv = import ./python-env.nix { inherit pkgs; };
debugTools = if config.debugMode then with pkgs; [
htop
ncdu
] else [];
in
pkgs.mkShell {
name = "${config.projectName}-dev";
packages = with pkgs; [
pythonEnv
git
vim
ripgrep
] ++ debugTools;
PROJECT_NAME = config.projectName;
PROJECT_VERSION = config.projectVersion;
FLASK_RUN_PORT = toString config.flaskPort;
}

Vous avez remarqué qu’on demande à utiliser la version figée de nixpkgs dans shell.nix et qu’on la transmet à python-env.nix via import ./python-env.nix { inherit pkgs; };.

Comprendre inherit

inherit pkgs est un raccourci pour pkgs = pkgs :

# Ces deux lignes sont équivalentes :
import ./python-env.nix { pkgs = pkgs; }
import ./python-env.nix { inherit pkgs; }

C’est très courant pour passer des variables qui portent le même nom.

Étape 13 : L’opérateur // (merge)

Fusionner des attrsets

L’opérateur // fusionne deux attrsets. Le côté droit écrase le gauche :

{ a = 1; b = 2; } // { b = 3; c = 4; }
# { a = 1; b = 3; c = 4; }

Cas pratique : surcharges de configuration

Créons un système de configuration avec des valeurs par défaut :

config.nix
let
defaults = {
projectName = "mon-projet";
debugMode = false;
flaskPort = 5000;
flaskHost = "127.0.0.1";
};
overrides = {
debugMode = true;
flaskPort = 8080;
};
in
defaults // overrides
# Résultat : debugMode=true, flaskPort=8080, le reste inchangé

Étape 14 : Fonctions utilitaires

Définir ses propres fonctions

Créons une fonction utilitaire pour formater les messages :

shell.nix
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") {} }:
let
config = import ./config.nix;
pythonEnv = import ./python-env.nix { inherit pkgs; };
# Fonction utilitaire - retourne une chaîne pour shellHook
formatBanner = title: version:
''
echo "╔════════════════════════════════╗"
echo "║ ${title} v${version}"
echo "╚════════════════════════════════╝"
'';
in
pkgs.mkShell {
packages = [ pythonEnv pkgs.git ];
shellHook = formatBanner config.projectName config.projectVersion;
}

Fonctions avec attrset en argument

Pour plus de clarté, utilisez des arguments nommés :

let
formatBanner = { title, version, debug ? false }:
''
Project: ${title} v${version}
'' + (if debug then "⚠️ DEBUG MODE\n" else "");
in
formatBanner {
title = "mon-api";
version = "1.0.0";
debug = true;
}

Étape 15 : Builtins essentiels

Nix fournit des fonctions intégrées (builtins). Voici les plus utiles :

map : transformer une liste

map (x: x * 2) [ 1 2 3 ]
# [ 2 4 6 ]

Cas pratique - ajouter un préfixe à chaque élément :

let
libs = [ "requests" "flask" "pytest" ];
in
map (lib: "python-${lib}") libs
# [ "python-requests" "python-flask" "python-pytest" ]

filter : filtrer une liste

builtins.filter (x: x > 2) [ 1 2 3 4 5 ]
# [ 3 4 5 ]

attrNames et attrValues

builtins.attrNames { a = 1; b = 2; c = 3; }
# [ "a" "b" "c" ]
builtins.attrValues { a = 1; b = 2; c = 3; }
# [ 1 2 3 ]

Test d’existence avec ?

L’opérateur ? teste si un attribut existe :

let config = { port = 8080; };
in
config ? port # true
config ? host # false

Combiné avec or pour des valeurs par défaut :

let config = { port = 8080; };
in
config.host or "localhost"
# "localhost"

Étape 16 : Configuration finale complète

Voici notre projet final avec tous les concepts :

mon-projet-python/
├── shell.nix
├── config.nix
├── python-env.nix
└── lib/
└── utils.nix
lib/utils.nix
{
# Retourne des commandes shell pour afficher une bannière
formatBanner = { title, version, debug ? false }:
''
echo "┌────────────────────────────────┐"
echo "│ 🐍 ${title} v${version}"
'' + (if debug then ''echo "│ ⚠️ Debug mode enabled"\n'' else "") + ''
echo "└────────────────────────────────┘"
'';
mkEnvVars = config: {
PROJECT_NAME = config.projectName;
PROJECT_VERSION = config.projectVersion;
FLASK_RUN_PORT = toString config.flaskPort;
FLASK_RUN_HOST = config.flaskHost;
FLASK_DEBUG = if config.debugMode then "1" else "0";
};
}
config.nix
let
defaults = {
projectName = "mon-api";
projectVersion = "1.0.0";
debugMode = false;
flaskPort = 5000;
flaskHost = "127.0.0.1";
};
# Surcharges pour le développement local
localOverrides = {
debugMode = true;
flaskPort = 8080;
};
in
defaults // localOverrides
python-env.nix
{ pkgs, config }:
let
basePackages = ps: [
ps.requests
ps.flask
ps.python-dotenv
];
devPackages = ps: [
ps.pytest
ps.black
ps.mypy
ps.ipython
];
allPackages = ps:
basePackages ps ++
(if config.debugMode then devPackages ps else []);
in
pkgs.python312.withPackages allPackages
shell.nix
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") {} }:
let
# Imports
config = import ./config.nix;
utils = import ./lib/utils.nix;
pythonEnv = import ./python-env.nix { inherit pkgs config; };
# Outils de base
baseTools = with pkgs; [
git
vim
ripgrep
jq
];
# Outils de debug
debugTools = with pkgs; [
htop
ncdu
curl
];
# Tous les paquets
allPackages = [ pythonEnv ] ++
baseTools ++
(if config.debugMode then debugTools else []);
in
pkgs.mkShell ({
name = "${config.projectName}-dev";
packages = allPackages;
shellHook = (utils.formatBanner {
title = config.projectName;
version = config.projectVersion;
debug = config.debugMode;
}) + "\necho \"Run 'flask run' to start the development server\"";
} // utils.mkEnvVars config)

Testez le résultat :

Terminal window
nix-shell
# Affiche la bannière et les infos
echo $FLASK_RUN_PORT # 8080
echo $FLASK_DEBUG # 1
python3 -c "import flask; print(flask.__version__)"

Récapitulatif des concepts

ConceptSyntaxeDescription
Fonctionsarg: corpsTout est fonction en Nix. Les fichiers .nix sont souvent des fonctions qui attendent pkgs ou d’autres arguments.
Attrsets{ clé = valeur; }Dictionnaires clé-valeur, structure fondamentale. On y accède avec . (ex: pkgs.git).
let … inlet x = 1; in xVariables locales immuables. Permettent de structurer et réutiliser des valeurs.
withwith pkgs;Importe les attributs d’un attrset dans le scope pour éviter les répétitions.
importimport ./file.nixCharge et évalue un fichier Nix. Permet la modularisation en plusieurs fichiers.
inheritinherit xRaccourci pour x = x. Permet de passer des variables du même nom.
Opérateur //a // bFusionne deux attrsets. Le droit écrase le gauche. Idéal pour les surcharges.
if-then-elseif c then a else bExpression conditionnelle. Le else est obligatoire car tout doit retourner une valeur.

Pour aller plus loin

Vous maîtrisez maintenant les bases du langage Nix en pratique. Voici les prochaines étapes pour approfondir :

  • Flakes Nix (guide à venir) : workflow moderne avec verrouillage des dépendances et reproductibilité garantie
  • NixOS : configurer un système d’exploitation entier de manière déclarative

Ressources complémentaires

RessourceDescription
Nix LanguageDocumentation officielle du langage
Nix PillsTutoriel approfondi pas à pas
nix.devGuides pratiques et bonnes pratiques

Conclusion

Le langage Nix peut sembler déroutant au premier abord, mais comme vous l’avez vu dans ce guide, ses concepts sont finalement simples : tout est expression, les attrsets structurent les données, les fonctions paramétrent les configurations, et l’immutabilité garantit la reproductibilité.

En construisant progressivement notre environnement Python, vous avez appris à lire et écrire du Nix de façon pratique. La prochaine étape naturelle sera d’explorer les flakes (guide à venir) pour bénéficier du verrouillage des dépendances et d’une structure de projet standardisée.