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 »{ projectName = "mon-api"; projectVersion = "2.0.0"; debugMode = false; listenPort = 5000;}overrides.nix — surcharges locales
Section intitulée « overrides.nix — surcharges locales »{ 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 sont celles de nixos-24.11 — figées, reproductibles.
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 --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 »| 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 |
À 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). - Pour les projets d’équipe, les flakes (guide suivant) remplacent avantageusement le pinning manuel.