Un shell.nix qui importe <nixpkgs> utilise la version du channel
installé sur votre machine. Rien ne garantit que votre collègue a le
même. Résultat : Python 3.12.13 chez vous, Python 3.12.8 chez lui. Ce
guide montre comment modulariser vos fichiers Nix et figer les
versions pour que tout le monde travaille avec exactement les mêmes
paquets.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Séparer la configuration dans plusieurs fichiers avec
import - Passer des arguments entre fichiers avec
inherit - Fusionner des configurations avec l'opérateur
// - Figer une version de nixpkgs avec
fetchTarballet un hash - Trouver le sha256 d'une archive avec
nix-prefetch-url - Comprendre pourquoi les flakes ont été créés
Dans quel contexte ?
Section intitulée « Dans quel contexte ? »Dès que votre projet Nix dépasse quelques lignes, vous rencontrez ces situations :
- votre
shell.nixmélange configuration, paquets Python, outils CLI et variables d'environnement dans un seul fichier qui grandit vite, - vous voulez réutiliser la même liste de paquets Python dans plusieurs projets sans copier-coller,
- deux développeurs ont des résultats différents parce que leurs channels Nix ne pointent pas vers la même version de nixpkgs,
- vous avez besoin d'une version précise d'un outil pour reproduire un bug signalé en CI.
La modularisation et le pinning résolvent chacun de ces problèmes.
Séparer la configuration avec import
Section intitulée « Séparer la configuration avec import »import charge et évalue un fichier .nix. Si le fichier contient un
attrset, il retourne cet attrset. Si le fichier contient une
fonction, il retourne cette fonction (qu'il faut ensuite appeler).
Extraire les variables dans config.nix
Section intitulée « Extraire les variables dans config.nix »{ projectName = "mon-api"; projectVersion = "2.0.0"; debugMode = true; listenPort = 5000;}Ce fichier est un simple attrset, import ./config.nix retourne
directement { projectName = "mon-api"; ... }.
Extraire les paquets Python dans python-packages.nix
Section intitulée « Extraire les paquets Python dans python-packages.nix »{ pkgs, config }:
let basePackages = ps: [ ps.requests ps.flask ];
devPackages = ps: [ ps.pytest ps.black ];inpkgs.python312.withPackages (ps: basePackages ps ++ (if config.debugMode then devPackages ps else []))Ce fichier est une fonction qui attend pkgs et config. Il faut
l'appeler avec les arguments : import ./python-packages.nix { inherit pkgs config; }.
Assembler dans shell.nix
Section intitulée « Assembler dans shell.nix »{ pkgs ? import <nixpkgs> {} }:
let config = import ./config.nix; pythonEnv = import ./python-packages.nix { inherit pkgs config; };
debugTools = if config.debugMode then with pkgs; [ htop ncdu ] else [];inpkgs.mkShell { name = "${config.projectName}-dev";
packages = with pkgs; [ pythonEnv git ripgrep ] ++ debugTools;
PROJECT_NAME = config.projectName; PROJECT_VERSION = config.projectVersion; LISTEN_PORT = toString config.listenPort;}nix-shell --run "echo \$PROJECT_NAME \$PROJECT_VERSION \$LISTEN_PORT && which htop"mon-api 2.0.0 5000/nix/store/zalk6ygj90a1d1jyk6rn4ha22fh19gzd-htop-3.4.1/bin/htopLa configuration est centralisée dans config.nix, un seul endroit à
modifier pour changer le nom du projet, la version ou le mode debug.
Fusionner des configurations avec //
Section intitulée « Fusionner des configurations avec // »L'opérateur // fusionne deux attrsets. Les clés du côté droit écrasent
celles du côté gauche. C'est idéal pour un système de surcharges :
defaults.nix, valeurs par défaut
Section intitulée « defaults.nix, valeurs par défaut »Ce fichier porte la configuration versionnée du projet, celle que tout le monde partage. Il doit rester exhaustif : chaque clé susceptible d'être surchargée doit y figurer avec une valeur sûre, ici debugMode = false. Une clé absente de defaults.nix mais consommée ailleurs provoquera une erreur d'attribut manquant dès qu'un développeur travaillera sans overrides.nix.
{ projectName = "mon-api"; projectVersion = "2.0.0"; debugMode = false; listenPort = 5000;}overrides.nix, surcharges locales
Section intitulée « overrides.nix, surcharges locales »Ce second fichier ne contient que les écarts par rapport aux valeurs par défaut, jamais la configuration complète. Deux clés suffisent ici, les autres sont héritées telles quelles. La fusion est superficielle : // remplace la valeur d'une clé en bloc, il ne fusionne pas récursivement deux attrsets imbriqués. Si debugMode était un attrset plutôt qu'un booléen, redéfinir une seule de ses sous-clés effacerait toutes les autres.
{ debugMode = true; listenPort = 8080;}Fusion dans shell.nix
Section intitulée « Fusion dans shell.nix »let defaults = import ./defaults.nix; overrides = import ./overrides.nix; config = defaults // overrides;in# config.debugMode = true (surchargé)# config.listenPort = 8080 (surchargé)# config.projectName = "mon-api" (hérité)nix-shell --run "echo \$LISTEN_PORT"8080Le port est bien 8080 (overrides) au lieu de 5000 (defaults), et
projectName reste "mon-api" car il n'est pas surchargé.
Cas d'usage : environnements multiples
Section intitulée « Cas d'usage : environnements multiples »Créez un fichier par environnement :
mon-projet/├── shell.nix├── defaults.nix├── overrides-dev.nix # debugMode=true, port 8080├── overrides-staging.nix # debugMode=false, port 5000└── python-packages.nixSélectionnez l'environnement depuis shell.nix :
config = defaults // (import ./overrides-dev.nix);Créer des fonctions utilitaires réutilisables
Section intitulée « Créer des fonctions utilitaires réutilisables »Quand une logique revient dans plusieurs projets, extrayez-la dans un fichier :
config: { PROJECT_NAME = config.projectName; PROJECT_VERSION = config.projectVersion; LISTEN_PORT = toString config.listenPort; DEBUG = if config.debugMode then "1" else "0";}let config = import ./config.nix; mkEnvVars = import ./lib/mk-env-vars.nix; envVars = mkEnvVars config;inpkgs.mkShell ({ packages = [ ... ];} // envVars)L'opérateur // fusionne l'attrset mkShell avec les variables
d'environnement, elles sont automatiquement exportées.
Pourquoi figer la version de nixpkgs
Section intitulée « Pourquoi figer la version de nixpkgs »Par défaut, import <nixpkgs> {} utilise le channel système. Ce channel
change à chaque mise à jour. Conséquence :
| Machine A (channel à jour) | Machine B (channel ancien) |
|---|---|
| Python 3.12.13 | Python 3.12.8 |
| git 2.53.0 | git 2.47.2 |
Deux mois d'écart dans les channels produisent des différences visibles. Pour un environnement reproductible, il faut figer la source.
Méthode 1 : fetchTarball avec un sha256
Section intitulée « Méthode 1 : fetchTarball avec un sha256 »Remplacez import <nixpkgs> {} par un import d'archive fixe :
{ pkgs ? import (fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/nixos-24.11.tar.gz"; sha256 = "sha256:1s2gr5rcyqvpr58vxdcb095mdhblij9bfzaximrva2243aal3dgx"; }) {}}:
pkgs.mkShell { packages = [ pkgs.python312 pkgs.git ];}Maintenant, peu importe le channel installé sur la machine :
nix-shell --run "python3 --version && git --version"Python 3.12.8git version 2.47.2Les versions obtenues sont celles de l'archive téléchargée, et non plus celles du channel local.
Méthode 2 : figer sur un commit précis
Section intitulée « Méthode 2 : figer sur un commit précis »Pour encore plus de précision, utilisez un commit spécifique de nixpkgs :
{ pkgs ? import (fetchTarball { url = "https://github.com/NixOS/nixpkgs/archive/4c1018dae018162ec878d42fec712642d214fdfa.tar.gz"; sha256 = "sha256:..."; # hash du commit }) {}}:
pkgs.mkShell { packages = [ pkgs.python312 ];}Un commit ne change jamais, c'est la garantie la plus forte de reproductibilité sans flakes.
Trouver le sha256 d'une archive nixpkgs
Section intitulée « Trouver le sha256 d'une archive nixpkgs »Deux méthodes :
Méthode 1 : laisser Nix calculer le hash
Section intitulée « Méthode 1 : laisser Nix calculer le hash »Mettez un sha256 vide et lancez nix-shell. L'erreur affiche le
bon hash :
sha256 = ""; # ← intentionnellement videerror: hash mismatch in file downloaded from '...': specified: sha256:000000000000000000000000000000 got: sha256:1s2gr5rcyqvpr58vxdcb095mdhblij9bfzaximrva2243aal3dgxCopiez la valeur got: dans votre fichier.
Méthode 2 : nix-prefetch-url
Section intitulée « Méthode 2 : nix-prefetch-url »nix-prefetch-url --unpack https://github.com/NixOS/nixpkgs/archive/nixos-24.11.tar.gz1s2gr5rcyqvpr58vxdcb095mdhblij9bfzaximrva2243aal3dgxPréfixez avec sha256: dans le fichier Nix.
Trouver un paquet dans une version précise
Section intitulée « Trouver un paquet dans une version précise »Vérifier la version dans un nixpkgs donné
Section intitulée « Vérifier la version dans un nixpkgs donné »nix eval interroge l'arbre de paquets sans rien construire ni télécharger : la réponse arrive en quelques secondes, ce qui en fait le contrôle à faire avant de figer un hash. Le drapeau --impure est indispensable ici, car <nixpkgs> est résolu via la variable NIX_PATH de votre machine, une entrée extérieure que le mode pur refuse par principe. La valeur retournée est donc celle de votre channel, pas celle de l'archive que vous vous apprêtez à figer : pour interroger une version précise, remplacez <nixpkgs> par le même fetchTarball que dans votre shell.nix.
nix eval --impure --expr '(import <nixpkgs> {}).python312.version'"3.12.13"Nixhub.io : chercher par version
Section intitulée « Nixhub.io : chercher par version »Le site Nixhub.io permet de rechercher quelle version de nixpkgs contient une version précise d'un paquet. Si vous avez besoin de Python 3.12.8 exactement, Nixhub vous indique quel commit ou quelle branche utiliser.
Structure recommandée pour un projet multi-fichiers
Section intitulée « Structure recommandée pour un projet multi-fichiers »mon-projet/├── shell.nix # point d'entrée, import des modules├── defaults.nix # configuration par défaut├── overrides.nix # surcharges locales (gitignored si besoin)├── python-packages.nix # paquets Python paramétrés└── lib/ └── mk-env-vars.nix # fonctions utilitairesPrincipes :
- Un fichier par responsabilité (configuration, paquets, utilitaires)
defaults.nixversionné,overrides.nixoptionnel et local- Les fonctions utilitaires dans
lib/ - Le pinning dans
shell.nix(ou dans unnixpkgs.nixdédié)
Limites du pinning classique : pourquoi les flakes existent
Section intitulée « Limites du pinning classique : pourquoi les flakes existent »Le pinning avec fetchTarball fonctionne, mais présente des inconvénients :
| Limite | Impact |
|---|---|
| Hash manuel | Vous devez chercher et coller le sha256 à chaque mise à jour |
| Pas de lock partagé | Chaque développeur doit avoir le bon hash dans le fichier |
| Mise à jour fastidieuse | Changer de version = modifier l'URL + recalculer le hash |
| Pas de composition | Combiner plusieurs sources (overlay, modules) est complexe |
Les flakes résolvent ces problèmes avec :
- un fichier
flake.lockautomatique et versionnable, - une commande
nix flake updatepour mettre à jour, - une composition native de plusieurs inputs.
Le pinning classique reste parfaitement valide pour les projets simples ou les environnements sans flakes. Pour les projets d'équipe, les flakes sont la solution recommandée.
Dépannage courant
Section intitulée « Dépannage courant »Deux familles d'erreurs se distinguent dans ce tableau, et elles ne se diagnostiquent pas de la même façon. Le hash mismatch et le cannot look up '<nixpkgs>' concernent la source des paquets : l'évaluation n'a même pas commencé. Les trois lignes suivantes concernent le langage Nix lui-même, et le message d'erreur cite alors le fichier et la ligne fautive. Réflexe utile dans les deux cas : relancer avec nix-shell --show-trace, qui déplie la pile d'évaluation et désigne le fichier importé réellement responsable, souvent différent de celui que vous éditiez.
| Symptôme | Cause probable | Solution |
|---|---|---|
hash mismatch | sha256 incorrect ou archive modifiée | Recalculer avec nix-prefetch-url --unpack |
cannot look up '<nixpkgs>' | Mode pur activé sans channel | Ajouter --impure ou figer avec fetchTarball |
import ./file.nix échoue | Chemin relatif incorrect | Vérifier que le fichier existe au bon endroit |
Argument manquant dans import | Fichier = fonction non appelée | Passer les arguments : import ./f.nix { inherit pkgs; } |
Attribut absent après // | Clé non définie dans defaults | Vérifier les deux attrsets avant fusion |
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »import ./file.nixcharge un fichier Nix, vous pouvez séparer configuration, paquets et utilitaires.- Si le fichier est une fonction, il faut l'appeler avec ses arguments :
import ./f.nix { inherit pkgs; }. - L'opérateur
//fusionne deux attrsets, idéal pour un système defaults + overrides. fetchTarballavec un sha256 fige la version de nixpkgs, plus aucune dérive entre machines.- Le hash se récupère avec
nix-prefetch-url --unpackou en laissant Nix le calculer (sha256 vide). - Une URL de branche (
nixos-24.11) n'est pas un pin durable : la branche avance et le hash finit par ne plus correspondre. Seul un commit est immuable. - Pour les projets d'équipe, les flakes remplacent avantageusement le pinning manuel grâce au
flake.lock.