Aller au contenu
Administration Linux medium

Créer des environnements de développement reproductibles avec Nix

15 min de lecture

Un fichier shell.nix à la racine de votre projet, un nix-shell (ou nix develop), et tout votre outillage est là — même version, même configuration, pour tous les membres de l’équipe. Pas de README à re-lire, pas de conflits de versions, pas de “ça marche sur ma machine”. Ce guide vous montre comment construire ces environnements pour Python, Node.js, Rust et des projets mixtes.

  • Comprendre le rôle de mkShell et la structure d’un shell.nix
  • Créer un environnement Python avec bibliothèques via withPackages
  • Créer un environnement Node.js et un environnement Rust
  • Passer des variables d’environnement et un shellHook au shell
  • Utiliser nix develop avec un flake comme alternative moderne
  • Charger l’environnement automatiquement avec direnv
  • Comparer nix-shell / nix develop avec venv, nvm et Docker

Chaque projet a ses dépendances : une version de Python, des bibliothèques, des outils CLI, une version de Node.js… Au quotidien, vous rencontrez ces situations :

  • un nouveau développeur rejoint le projet et passe une demi-journée à installer les bons outils,
  • deux projets exigent des versions différentes de Node.js ou Python,
  • un script CI échoue parce que la version de jq diffère du poste local,
  • le README dit “installer gcc, cmake, libssl-dev, python3.12…” mais personne ne le maintient à jour.

Avec Nix, un seul fichier (shell.nix ou flake.nix) décrit l’environnement de manière reproductible. Il est versionné avec le code et garantit que tout le monde travaille avec le même outillage.

pkgs.mkShell est une fonction qui crée un environnement de développement. Elle attend un attrset avec :

AttributRôle
packagesListe des paquets disponibles dans le shell
shellHookScript exécuté à l’entrée dans le shell
nameNom affiché (optionnel)
toute autre cléDevient une variable d’environnement
shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs; [
python312
git
vim
];
}

Pour entrer dans l’environnement :

Fenêtre de terminal
nix-shell
Résultat
Python 3.12.13
git version 2.53.0

Quand vous tapez exit, les outils disparaissent — ils ne sont accessibles que dans le shell Nix.

Python “nu” ne suffit pas : vous avez besoin de requests, flask, pytest… La fonction withPackages crée un Python qui inclut les bibliothèques demandées.

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
ripgrep
];
PROJECT_NAME = "demo-api";
FLASK_DEBUG = "1";
shellHook = ''
echo "=================================="
echo " Projet: $PROJECT_NAME"
echo " Python: $(python3 --version 2>&1)"
echo " Flask debug: $FLASK_DEBUG"
echo "=================================="
'';
}
Fenêtre de terminal
nix-shell
Résultat
==================================
Projet: demo-api
Python: Python 3.12.13
Flask debug: 1
==================================

Vérifions que les bibliothèques sont bien disponibles :

Fenêtre de terminal
python3 -c "import requests; print(f'requests {requests.__version__}')"
python3 -c "import pytest; print(f'pytest {pytest.__version__}')"
Résultat
requests 2.32.5
pytest 9.0.2

Les bibliothèques sont installées dans le store Nix, pas dans un venv classique. Elles disparaissent quand vous quittez le shell.

withPackages attend une fonction qui reçoit l’ensemble des paquets Python disponibles (ps) et retourne la liste de ceux que vous voulez :

pkgs.python312.withPackages (ps: [
ps.requests # ← sélectionne requests dans l'ensemble Python
ps.flask # ← sélectionne flask
])

La syntaxe ps: [ ... ] est une fonction anonyme (lambda) — un concept couvert dans le guide du langage Nix.

Tous les paquets Python nixpkgs sont listés avec le préfixe python3Packages. :

Fenêtre de terminal
nix search nixpkgs python3Packages.requests

Dans un shell.nix, on utilise ps.requests (raccourci fourni par withPackages).

shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs; [
nodejs_22
jq
curl
];
shellHook = "echo 'Node.js:' $(node --version)";
}
Fenêtre de terminal
nix-shell --run "node --version && jq --version"
Résultat
v22.22.2
jq-1.8.1
shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs; [
rustc
cargo
gcc
];
RUST_BACKTRACE = "1";
shellHook = "echo 'Rust:' $(rustc --version)";
}
Fenêtre de terminal
nix-shell --run "rustc --version && cargo --version"
Résultat
rustc 1.94.0 (4a4ef493e 2026-03-02)
cargo 1.94.0 (85eff7c80 2026-01-15)

La variable RUST_BACKTRACE = "1" est automatiquement exportée — toute clé inconnue de mkShell devient une variable d’environnement.

Ajoutez simplement des clés à l’attrset de mkShell :

pkgs.mkShell {
packages = [ ... ];
# Deviennent des variables d'environnement
PROJECT_NAME = "mon-api";
FLASK_DEBUG = "1";
FLASK_RUN_PORT = "5000";
DATABASE_URL = "postgresql://localhost:5432/dev";
}
Fenêtre de terminal
nix-shell --run "echo \$PROJECT_NAME \$FLASK_RUN_PORT"
Résultat
mon-api 5000

Le shellHook s’exécute à chaque entrée dans le shell. Il sert à :

  • afficher un message de bienvenue,
  • activer un environnement,
  • vérifier des prérequis,
  • créer des fichiers temporaires.
shellHook = ''
echo "=================================="
echo " Projet: $PROJECT_NAME"
echo " Python: $(python3 --version 2>&1)"
echo "=================================="
'';

nix develop : l’alternative moderne avec les flakes

Section intitulée « nix develop : l’alternative moderne avec les flakes »

nix develop est l’équivalent de nix-shell dans le monde des flakes. Il nécessite un fichier flake.nix (et un dépôt Git).

flake.nix
{
description = "Environnement de développement Node.js";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
nodejs_22
jq
curl
];
shellHook = "echo 'Node.js:' $(node --version)";
};
};
}
  1. Initialisez le dépôt (les flakes exigent Git) :

    Fenêtre de terminal
    git init && git add flake.nix
  2. Entrez dans l’environnement :

    Fenêtre de terminal
    nix develop
    Résultat
    Node.js: v22.22.2
  3. Vérifiez le flake.lock généré :

    Fenêtre de terminal
    cat flake.lock | head -10
    Extrait de flake.lock
    {
    "nodes": {
    "nixpkgs": {
    "locked": {
    "lastModified": 1775710090,
    "narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+...",
    "rev": "4c1018dae018162ec878d42fec712642d214fdfa",

Le flake.lock fige automatiquement la version de nixpkgs. C’est le principal avantage sur shell.nix classique.

nix-shell vs nix develop : quand utiliser lequel ?

Section intitulée « nix-shell vs nix develop : quand utiliser lequel ? »
Critèrenix-shell + shell.nixnix develop + flake.nix
VerrouillageManuel (fetchTarball + sha256)Automatique (flake.lock)
PrérequisAucunDépôt Git obligatoire
ReproductibilitéBonne si pinnéGarantie par le lock
SimplicitéPlus simple à démarrerPlus verbeux au début
RecommandationProjets simples, scriptsProjets d’équipe, CI

Quand le shell.nix grandit, séparez la configuration dans des fichiers dédiés.

mon-projet/
├── shell.nix # point d'entrée
├── config.nix # variables du projet
└── python-env.nix # environnement Python
config.nix
{
projectName = "demo-api";
projectVersion = "1.0.0";
debugMode = true;
flaskPort = 5000;
}
python-env.nix
{ pkgs }:
pkgs.python312.withPackages (ps: [
ps.requests
ps.flask
ps.pytest
])

Ce fichier est une fonction qui attend pkgs en argument.

shell.nix
{ pkgs ? import <nixpkgs> {} }:
let
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
ripgrep
] ++ debugTools;
PROJECT_NAME = config.projectName;
PROJECT_VERSION = config.projectVersion;
FLASK_RUN_PORT = toString config.flaskPort;
shellHook = ''
echo "Projet: ${config.projectName} v${config.projectVersion}"
echo "Debug: ${if config.debugMode then "ON" else "OFF"}"
'';
}
Fenêtre de terminal
nix-shell --run "echo \$PROJECT_NAME \$PROJECT_VERSION \$FLASK_RUN_PORT && which htop"
Résultat
Projet: demo-api v1.0.0
Debug: ON
demo-api 1.0.0 5000
/nix/store/zalk6ygj90a1d1jyk6rn4ha22fh19gzd-htop-3.4.1/bin/htop

inherit pkgs est un raccourci pour pkgs = pkgs — il transmet la variable du même nom. La modularisation est plus approfondie dans le guide sur l’import, la factorisation et le pinning.

Charger l’environnement automatiquement avec direnv

Section intitulée « Charger l’environnement automatiquement avec direnv »

direnv charge automatiquement l’environnement Nix quand vous entrez dans le dossier du projet. Plus besoin de taper nix-shell manuellement.

  1. Installez direnv et nix-direnv :

    Fenêtre de terminal
    nix profile add nixpkgs#direnv nixpkgs#nix-direnv
  2. Activez le hook shell (ajoutez à ~/.bashrc ou ~/.zshrc) :

    Fenêtre de terminal
    eval "$(direnv hook bash)" # ou zsh
  3. Créez .envrc à la racine du projet :

    .envrc
    use nix

    Ou pour les flakes :

    .envrc
    use flake
  4. Autorisez le répertoire :

    Fenêtre de terminal
    direnv allow

Désormais, chaque cd mon-projet/ charge automatiquement l’environnement Nix, et cd .. le décharge. Les outils apparaissent et disparaissent sans intervention.

Hors du shell Nix, seul le Python système (Debian) est disponible. Dans le shell, Nix fournit ses propres versions — tout est isolé :

Démonstration d'isolation
=== HORS nix-shell ===
htop: introuvable
Python 3.11.2 ← Python Debian
=== DANS nix-shell ===
/nix/store/.../bin/htop ← htop Nix
Python 3.12.13 ← Python Nix
=== APRÈS nix-shell ===
htop: disparu ← retour à l'état initial

Chaque projet peut avoir sa propre version de Python, Node.js ou Rust sans conflits. Plus besoin de pyenv, nvm, rustup pour gérer les versions par projet.

Critèrevenv / nvmDockerNix (shell.nix / flake.nix)
PortéeUn seul langageSystème completTous les outils
OverheadAucunConteneur (RAM, disque)Aucun (accès direct)
Reproductibilitérequirements.txt / .nvmrcDockerfileshell.nix / flake.lock
IsolationBibliothèques uniquementTotale (FS, réseau…)Binaires et bibliothèques
CIpip install + nvm usedocker runnix-shell —run
PartageFichier texteImage (Docker Hub)Fichier Nix versionné
  • venv / nvm : limités à un seul langage ; Nix gère tout.
  • Docker : isolation totale mais plus lourd ; Nix n’ajoute pas de couche.
  • Nix : le meilleur compromis pour les environnements de développement.
SymptômeCause probableSolution
nix-shell ne trouve pas un paquetNom incorrectnix search nixpkgs <nom> pour trouver le bon nom
import <nixpkgs> échouePas de channel configurénix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs && nix-channel --update
Python ne voit pas les bibliothèqueswithPackages manquantUtiliser python312.withPackages (ps: [ ps.xxx ])
Changements dans shell.nix non pris en compteCache direnvdirenv reload ou quitter et relancer nix-shell
nix develop échouePas de dépôt Gitgit init && git add flake.nix
shellHook ne s’exécute pasSyntaxe Nix incorrecteVérifier les '' (multilignes) et les ${} (interpolation)
  • Un fichier shell.nix ou flake.nix à la racine du projet décrit l’environnement complet : langages, outils CLI, variables d’environnement.
  • pkgs.mkShell est la fonction centrale — elle prend une liste de paquets, un shellHook et des variables d’environnement.
  • python312.withPackages crée un Python avec ses bibliothèques, sans pip install.
  • Toute clé inconnue de mkShell devient une variable d’environnement exportée.
  • nix develop (flakes) ajoute le verrouillage automatique des versions — privilégiez-le pour les projets d’équipe.
  • direnv charge l’environnement automatiquement au cd — c’est le confort ultime au quotidien.
  • Les outils n’existent que dans le shell Nix — l’isolation est totale.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn