ITSkillsCenter
Business Digital

Dev-shells par projet avec nix develop : tutoriel pas-à-pas

9 دقائق للقراءة

📍 Guide principal : NixOS pour développeurs et serveurs : reproductibilité totale en 2026
Cet article approfondit le pattern dev-shell vu rapidement dans le tutoriel Flakes : essentiels et premier projet reproductible.

Pourquoi un dev-shell par projet change la vie d’un développeur

Si vous travaillez sur plusieurs projets en parallèle, vous connaissez la galère : un projet exige Node 18, un autre Node 22, un troisième a besoin de Python 3.10 alors que votre OS est passé à 3.12. Vous installez nvm, pyenv, asdf, rbenv, sdkman, et chaque outil ajoute son propre code dans votre ~/.zshrc. Quand un coéquipier ouvre votre projet, il refait la même installation, et il finit par avoir presque la même version, mais pas tout à fait.

Un dev-shell Nix renverse l’approche. Au lieu d’installer les outils globalement et de jongler avec les versions, le projet déclare ses besoins exacts dans un flake.nix. nix develop charge un shell où ces outils sont disponibles, et seulement ceux-là. Pas d’installation système, pas de pollution du $HOME, pas de conflit entre projets. Cloner un projet, taper nix develop, le projet compile. Cinq minutes au lieu de deux jours d’onboarding.

Ce tutoriel construit pas à pas un dev-shell complet pour un projet typique : un backend Node.js + TypeScript, une base PostgreSQL lancée automatiquement avec le shell, des scripts de seed et de migration, l’intégration avec direnv pour qu’aucun coéquipier n’oublie de charger l’environnement.

Prérequis

  • Nix installé avec flakes activés (cf. Flakes : essentiels).
  • Un dossier de projet existant ou à créer.
  • Connaissance des concepts flake.nix et devShells.

Étape 1 — Le dev-shell minimal pour un projet Node + TypeScript

Démarrez par le strict minimum pour comprendre la mécanique. Dans le dossier du projet :

git init
nix flake init

Remplacez flake.nix par cette version qui expose Node 22, pnpm et TypeScript globalement :

{
  description = "Backend Node + TypeScript";
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
    flake-utils.url = "github:numtide/flake-utils";
  };
  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = import nixpkgs { inherit system; };
      in {
        devShells.default = pkgs.mkShell {
          packages = with pkgs; [ nodejs_22 pnpm typescript ];
          shellHook = ''
            echo "Bienvenue dans ''${PROJECT_NAME:-le projet}"
            echo "Node $(node --version)"
          '';
        };
      });
}

Activez le shell :

git add flake.nix
nix develop

À l’invite, which node renvoie un chemin sous /nix/store/...nodejs-22.x.x/bin/node. which tsc trouve le compilateur TypeScript. node --version doit afficher la version 22.x.x. Sortez avec exit ; les commandes node et tsc ne sont plus dans votre PATH.

Étape 2 — Ajouter PostgreSQL en service local du shell

Pour un backend qui a besoin d’une base, le pattern le plus propre est de la lancer comme service du shell — pas comme service système, pas dans un container Docker. PostgreSQL démarre quand vous entrez dans le shell, s’arrête quand vous sortez, et stocke ses données dans le dossier du projet.

devShells.default = pkgs.mkShell {
  packages = with pkgs; [ nodejs_22 pnpm typescript postgresql_17 ];
  shellHook = ''
    export PGDATA=$PWD/.pg
    export PGHOST=$PWD/.pg
    export PGPORT=5433
    export DATABASE_URL="postgresql://localhost:5433/dev"

    if [ ! -d $PGDATA ]; then
      echo "Initialisation de PostgreSQL..."
      initdb -D $PGDATA --auth=trust --no-locale --encoding=UTF8 >/dev/null
      echo "unix_socket_directories = '$PWD/.pg'" >> $PGDATA/postgresql.conf
    fi

    pg_ctl -D $PGDATA -l $PGDATA/postgres.log start
    trap "pg_ctl -D $PGDATA stop" EXIT

    if ! psql -lqt | cut -d \| -f 1 | grep -qw dev; then
      createdb dev
    fi
  '';
};

À l’entrée du shell, le hook initialise la base si nécessaire, lance le serveur sur le port 5433 (pour ne pas entrer en conflit avec un Postgres système sur 5432), crée la base dev, et programme l’arrêt à la sortie via trap. Vos données vivent dans .pg/ à la racine du projet — ajoutez-le à .gitignore.

Étape 3 — Tester l’intégration

Sortez du shell, supprimez .pg/ si vous l’aviez, puis :

nix develop
psql $DATABASE_URL -c "SELECT version();"

La sortie affiche la version de PostgreSQL 17. Tapez exit ; pg_ctl status -D .pg doit indiquer « no server running ». Le shell a tué le serveur en sortant. Réentrez avec nix develop ; le serveur redémarre, les données sont préservées.

Étape 4 — Ajouter direnv pour automatiser l’entrée

Taper nix develop à chaque cd est fastidieux. direnv + nix-direnv charge le shell automatiquement à l’entrée du dossier. Si vous avez suivi le tutoriel Home-Manager, c’est déjà installé ; sinon :

nix-env -iA nixpkgs.direnv nixpkgs.nix-direnv
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc

Dans le projet :

echo 'use flake' > .envrc
direnv allow

Au prochain cd dans le dossier, le shell se reconfigure ; au cd sortant, votre environnement parent revient. Aucun script à maintenir, aucun oubli possible.

Étape 5 — Variables d’environnement secrètes via direnv

Vos clés d’API n’ont rien à faire dans flake.nix (qui est versionné). direnv permet de les charger via .envrc tout en les gardant hors du repo. Ajoutez à .envrc :

use flake
dotenv_if_exists .env.local

Ajoutez .env.local à votre .gitignore, et créez-le avec vos secrets :

STRIPE_SECRET_KEY=sk_test_xxx
GITHUB_TOKEN=ghp_xxx

Au prochain direnv allow, ces variables sont disponibles dans le shell, jamais commit. Le pattern marche pour les tokens de dev ; pour les secrets de production, voir le tutoriel sur agenix.

Étape 6 — Multi-langages dans le même flake

Beaucoup de projets ont un backend dans un langage et un frontend dans un autre. Plutôt qu’un devShell géant, déclarez-en plusieurs et activez le bon avec nix develop .#<nom> :

devShells = {
  default = pkgs.mkShell {
    packages = with pkgs; [ git curl ];
  };
  backend = pkgs.mkShell {
    packages = with pkgs; [ nodejs_22 pnpm typescript postgresql_17 ];
  };
  frontend = pkgs.mkShell {
    packages = with pkgs; [ nodejs_22 pnpm ];
  };
  data = pkgs.mkShell {
    packages = with pkgs; [ python312 python312Packages.pandas duckdb ];
  };
};

Dans le terminal du backend : nix develop .#backend. Dans celui des data scripts : nix develop .#data. Chaque shell est isolé, on ne mélange pas les versions de Node entre l’app et les outils.

Étape 7 — Partager les hooks et scripts dans le shell

Tous les coéquipiers ont les mêmes scripts si vous les déclarez dans le shell. Plutôt que d’imposer un Makefile, exposez vos commandes via des scripts construits avec writeShellApplication :

let
  pkgs = import nixpkgs { inherit system; };
  reset-db = pkgs.writeShellApplication {
    name = "reset-db";
    runtimeInputs = [ pkgs.postgresql_17 ];
    text = ''
      dropdb --if-exists dev
      createdb dev
      psql dev < db/seed.sql
    '';
  };
in {
  devShells.default = pkgs.mkShell {
    packages = with pkgs; [ nodejs_22 reset-db ];
  };
}

Dans le shell, taper reset-db exécute le script. Plus besoin de scripts/reset-db.sh, plus de problème de permissions sur le fichier.

Étape 8 — Pinner et mettre à jour

Le flake.lock fige les versions au commit près. Pour mettre à jour Node ou PostgreSQL, vous mettez à jour nixpkgs :

nix flake update
nix develop
node --version

Si la nouvelle version casse le projet, git checkout flake.lock ramène à l'ancienne. Vos coéquipiers ne sont impactés que quand vous commit le nouveau lock — c'est un changement explicite, pas une dérive silencieuse.

Erreurs fréquentes

Symptôme Cause Solution
error: 'pkgs.nodejs_24' is not available Version pas encore dans la nixpkgs pinnée. nix flake update ou utiliser nodejs_22.
PostgreSQL refuse de démarrer (port occupé) Un autre Postgres tourne sur 5433. Changer PGPORT dans le shellHook.
direnv ne charge pas le shell direnv allow oublié. Lancer direnv allow dans le dossier.
shellHook ne s'exécute pas avec direnv nix-direnv pas activé. Vérifier que nix-direnv est installé et hooké dans le shell parent.
Le shell est très lent à démarrer Évaluation Nix complète à chaque entrée. nix-direnv met en cache automatiquement ; vérifier que nix-direnv et pas juste direnv est utilisé.

Pourquoi dev-shells bat docker compose pour le développement

Beaucoup d'équipes lancent leur stack de dev avec docker compose up. Ça marche mais a trois faiblesses. Le démarrage est lent : tirer les images, attendre les health checks, c'est 30 à 90 secondes. Un dev-shell Nix avec un Postgres local démarre en moins de 3 secondes. L'isolation est un piège pour le débogage : votre éditeur tourne sur l'hôte, le code dans le container, les chemins ne matchent jamais, attacher un debugger devient un sport. La reproductibilité est fragile : FROM node:22 récupère une image différente d'un mois à l'autre selon les patches de sécurité ; un dev-shell Nix est figé au hash près.

Docker reste pertinent pour produire une image de production ou pour isoler des services tiers (Kafka, Elasticsearch) qui ne se package pas bien en Nix. Pour le développement quotidien, dev-shells gagne sur la vitesse, la précision et le confort.

Cas d'usage : équipe distribuée avec contraintes différentes

Une équipe avec des développeurs sur Linux, macOS et WSL2 souffre traditionnellement des différences d'environnement. Un dev-shell géré par flake résout 90 % du problème : tous les développeurs ont la même version de Node, la même version de Postgres, les mêmes outils CLI. Les 10 % restants concernent les outils GUI ou system-specific (Docker Desktop sur macOS, systemd sur Linux), qui de toute façon n'ont pas leur place dans un environnement de dev.

Le pattern qui marche en pratique : un dev-shell minimal partagé, et chaque dev complète avec ses préférences via Home-Manager (zsh, alias, plugins Neovim). Le projet reste portable, l'expérience individuelle reste personnalisée.

Suite logique

Avec dev-shells maîtrisés, vos projets sont reproductibles côté développement. Pour passer à la production, le tutoriel nixos-rebuild remote : déployer sur un serveur distant montre comment le même flake peut décrire et déployer un serveur. Pour les secrets en production, Gérer les secrets avec agenix est la suite. Le guide principal NixOS reste l'index général.

Références officielles

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité