Aller au contenu
Administration Linux medium

Comprendre et utiliser les flakes Nix

23 min de lecture

Les flakes sont le mécanisme de reproductibilité native de Nix. Un fichier flake.nix déclare des dépendances versionnées (les inputs), et flake.lock verrouille leurs révisions exactes. Résultat : chaque membre de l'équipe, chaque serveur CI et chaque machine de production obtient exactement le même environnement. Ce guide couvre la structure complète d'un flake, les commandes de gestion, et la migration depuis shell.nix.

  • Créer un flake avec nix flake init et comprendre sa structure
  • Déclarer des inputs (nixpkgs, flake-utils) et les verrouiller avec flake.lock
  • Définir des outputs : devShells, packages, apps
  • Construire, exécuter et inspecter un flake
  • Convertir un projet shell.nix classique vers les flakes
  • Supporter plusieurs architectures avec flake-utils

Les channels Nix classiques, décrits dans Import, factorisation et pinning, posent un problème fondamental : chaque machine peut pointer vers une révision différente de nixpkgs. Même avec fetchTarball et un hash, le pinning reste manuel et fragile.

Les flakes résolvent ce problème en pratique quotidienne :

  • Votre collègue clone votre dépôt et obtient les mêmes versions de Python, Node.js et tous les outils, automatiquement
  • La CI construit avec les mêmes dépendances que votre poste, sans configuration supplémentaire
  • Vous mettez à jour nixpkgs d'un seul nix flake update, avec un diff vérifiable dans flake.lock
  • Vous composez plusieurs sources (nixpkgs stable, un overlay privé, un outil depuis GitHub) dans un seul fichier déclaratif

Les flakes sont une fonctionnalité expérimentale qu'il faut activer explicitement. Si vous avez suivi le guide d'installation, c'est déjà fait.

Vérifiez votre configuration :

Fenêtre de terminal
grep experimental-features ~/.config/nix/nix.conf 2>/dev/null || \
grep experimental-features /etc/nix/nix.conf 2>/dev/null
experimental-features = nix-command flakes

Si la ligne est absente :

Fenêtre de terminal
mkdir -p ~/.config/nix
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf

La commande nix flake init génère un flake.nix de base dans le répertoire courant. Le répertoire doit être un dépôt Git, les flakes ne fonctionnent qu'avec des fichiers suivis par Git.

  1. Créez un répertoire et initialisez Git :

    Fenêtre de terminal
    mkdir mon-projet && cd mon-projet
    git init
  2. Générez le flake de base :

    Fenêtre de terminal
    nix flake init
    wrote: "/home/lab/mon-projet/flake.nix"
  3. Examinez le fichier généré :

    Fenêtre de terminal
    cat flake.nix
    {
    description = "A very basic flake";
    inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    };
    outputs = { self, nixpkgs }: {
    packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
    packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
    };
    }
  4. Ajoutez le fichier à Git (obligatoire pour que Nix le voie) :

    Fenêtre de terminal
    git add flake.nix

Un flake.nix est une expression Nix qui retourne un attribute set avec trois clés :

{
description = "Description du projet"; # texte libre
inputs = {
# Les dépendances et leur source
};
outputs = { self, ... }:
# Ce que le flake produit
{ };
}
CléRôleObligatoire
descriptionTexte affiché par nix flake metadataNon (recommandé)
inputsDépendances externes avec leur URLOui
outputsFonction qui retourne packages, shells, apps…Oui

La clé outputs est une fonction dont le premier argument est toujours self (le flake lui-même), suivi d'un argument par input déclaré.

Les inputs déclarent les sources dont votre flake dépend. La plus courante est nixpkgs.

Chaque input s'écrit sous forme d'URL de flake, un format propre à Nix qui encode à la fois le protocole, l'emplacement et la révision voulue. Les quatre onglets ci-dessous couvrent les cas que vous rencontrerez : un dépôt GitHub (le cas courant), un dépôt Git quelconque pour du code interne, un chemin local pour un monorepo, et une archive pour du code qui n'est pas versionné dans Git. Retenez surtout le troisième segment de l'URL GitHub : il accepte une branche, un tag ou un commit, et c'est lui qui détermine ce que nix flake update ira chercher.

inputs = {
# Branche spécifique
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
# Branche stable
nixpkgs-stable.url = "github:nixos/nixpkgs/nixos-24.11";
# Un outil tiers
flake-utils.url = "github:numtide/flake-utils";
};

Format : github:propriétaire/dépôt/branche

Vos inputs ont eux-mêmes des inputs. home-manager déclare son propre nixpkgs, flake-utils déclare un input systems. Sans intervention, Nix verrouille deux révisions différentes de nixpkgs dans votre flake.lock : la vôtre et celle de home-manager. Vous vous retrouvez avec deux jeux de paquets côte à côte dans le store, et des incohérences difficiles à diagnostiquer quand les deux versions divergent. La directive follows redirige un input transitif vers un input que vous contrôlez :

inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager.url = "github:nix-community/home-manager";
# home-manager utilisera NOTRE nixpkgs au lieu du sien
home-manager.inputs.nixpkgs.follows = "nixpkgs";
};

Le même mécanisme sert à restreindre la liste des architectures. flake-utils ne dépend pas de nixpkgs, son unique input s'appelle systems et pointe par défaut vers nix-systems/default (les quatre plateformes standards). En le faisant suivre un input à vous, vous limitez les outputs générés à ce qui vous intéresse :

inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
systems.url = "github:nix-systems/x86_64-linux";
flake-utils.inputs.systems.follows = "systems";
};

Sans follows, chaque input télécharge sa propre copie de ses dépendances. Avec follows, vous déduplicez, vous réduisez la taille du flake.lock et vous garantissez qu'une mise à jour de nixpkgs s'applique partout d'un coup.

Le fichier flake.lock : reproductibilité garantie

Section intitulée « Le fichier flake.lock : reproductibilité garantie »

La première évaluation d'un flake génère automatiquement un flake.lock. Ce fichier JSON enregistre la révision exacte de chaque input :

{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1775710090,
"narHash": "sha256-ar3rofg+awPB8QXDaFJhJ2jJhu+KqN/PRCXeyuXR76E=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4c1018dae018162ec878d42fec712642d214fdfa",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

Trois informations clés :

  • rev : le commit exact de nixpkgs utilisé
  • narHash : le hash cryptographique du contenu, garantit l'intégrité
  • original : la source telle que déclarée dans inputs

La fonction outputs retourne un attribute set dont les clés suivent une convention standardisée. Voici un flake complet avec les trois outputs les plus courants :

{
description = "Projet Python avec devShell et package";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
python = pkgs.python312;
in {
# Environnement de développement
devShells.${system}.default = pkgs.mkShell {
packages = [
(python.withPackages (ps: [ ps.requests ps.pytest ]))
pkgs.curl
pkgs.jq
];
shellHook = ''
echo "Environnement Python pret"
export PROJECT_NAME=mon-projet
'';
};
# Paquet construisible
packages.${system}.default = pkgs.writeShellScriptBin "hello-projet" ''
echo "Bienvenue dans mon-projet !"
'';
# Application exécutable
apps.${system}.default = {
type = "app";
program = "${self.packages.${system}.default}/bin/hello-projet";
};
};
}

Les noms d'outputs ne sont pas libres : les commandes nix cherchent des chemins d'attributs précis. nix develop sans argument regarde devShells.<system>.default, nix build regarde packages.<system>.default. Un devShell rangé sous packages ne sera jamais trouvé. Lisez le tableau par sa colonne du milieu : elle indique quelle commande active quel output. Les deux lignes marquées « aucun » (overlays et nixosModules) sont des outputs consommés par d'autres flakes, jamais lancés directement.

OutputCommande associéeRôle
devShells.<system>.defaultnix developEnvironnement de développement
packages.<system>.defaultnix buildPaquet à construire
apps.<system>.defaultnix runApplication à exécuter
overlays.defaultaucunModification de nixpkgs
nixosConfigurations.<nom>nixos-rebuildConfiguration NixOS complète
nixosModules.defaultaucunModule NixOS réutilisable

nix flake show évalue les outputs et affiche leur arborescence. C'est la première commande à lancer sur un flake que vous ne connaissez pas : elle répond à « qu'est-ce que ce dépôt expose, et sous quel nom ». Les feuilles de l'arbre portent le type de chaque output (app, package, development environment), et c'est exactement ce nom d'attribut que vous passerez ensuite à nix build .#<nom>.

Fenêtre de terminal
nix flake show
git+file:///home/lab/demo-flake-devshell
├───apps
│ └───x86_64-linux
│ └───default: app
├───devShells
│ └───x86_64-linux
│ └───default: development environment 'nix-shell'
└───packages
└───x86_64-linux
└───default: package 'hello-projet'

Consulter les métadonnées avec nix flake metadata

Section intitulée « Consulter les métadonnées avec nix flake metadata »

Là où nix flake show décrit les sorties, nix flake metadata décrit les entrées. La ligne à surveiller est l'arbre Inputs : il affiche la révision réellement verrouillée et sa date, pas la branche demandée. C'est la façon la plus rapide de répondre à « depuis combien de temps ce projet n'a pas mis à jour nixpkgs », sans ouvrir le flake.lock. Le champ Path donne le chemin du store où la copie du dépôt a été importée.

Fenêtre de terminal
nix flake metadata
Resolved URL: git+file:///home/lab/demo-flake-devshell
Description: Projet Python avec devShell et package
Path: /nix/store/d9pvgm44r0dnqlg8whgdpzd7q73ihayj-source
Inputs:
└───nixpkgs: github:nixos/nixpkgs/4c1018dae018162ec878d42fec712642d214fdfa (2026-04-09)

nix develop construit toutes les dépendances du devShell puis ouvre un sous-shell dont le PATH pointe vers le store. Le premier lancement peut prendre plusieurs minutes s'il faut télécharger ou compiler ; les suivants sont immédiats grâce au cache du store. Vous restez dans votre shell habituel, seules les variables d'environnement changent : sortez avec exit ou Ctrl+D et votre PATH d'origine revient.

Fenêtre de terminal
nix develop
Environnement Python pret

Le shellHook s'exécute et toutes les dépendances sont disponibles :

Fenêtre de terminal
python3 --version && echo $PROJECT_NAME
Python 3.12.13
mon-projet
Fenêtre de terminal
nix build
building '/nix/store/8s05glip2sx5zb5gj1n6l0r2ggjn7h96-hello-projet.drv'...

Le résultat est un lien symbolique ./result pointant vers le store :

Fenêtre de terminal
ls -la result
result -> /nix/store/10s5j3mfdg22k1597x580qrhprnzcjwb-hello-projet
Fenêtre de terminal
./result/bin/hello-projet
Bienvenue dans mon-projet !
Fenêtre de terminal
nix run
Bienvenue dans mon-projet !

nix run combine nix build + exécution du binaire par défaut en une seule commande.

Fenêtre de terminal
nix flake check
checking flake output 'devShells'...
checking derivation devShells.x86_64-linux.default...
checking flake output 'packages'...
checking derivation packages.x86_64-linux.default...
checking flake output 'apps'...
checking app 'apps.x86_64-linux.default'...
all checks passed!

nix flake check évalue toutes les dérivations et vérifie la conformité des outputs. Indispensable dans une pipeline CI.

Pour mettre à jour tous les inputs :

Fenêtre de terminal
nix flake update

Pour mettre à jour un seul input :

Fenêtre de terminal
nix flake update nixpkgs

Après la mise à jour, vérifiez le diff de flake.lock avec git diff flake.lock avant de commiter.

Convertir un projet shell.nix existant vers les flakes

Section intitulée « Convertir un projet shell.nix existant vers les flakes »

Si vous avez un shell.nix classique qui utilise <nixpkgs>, voici comment le migrer vers un flake.

shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
packages = with pkgs; [
python312
python312Packages.requests
git
curl
];
shellHook = ''
echo "Environnement dev classique"
'';
}

Ce fichier fonctionne avec nix-shell, mais les versions dépendent du channel actif sur chaque machine.

La liste de paquets est reprise telle quelle, y compris le with pkgs;. Trois choses seulement changent : la source de pkgs devient un input verrouillé au lieu de <nixpkgs>, le mkShell est rangé sous l'attribut devShells.<system>.default attendu par nix develop, et le system est écrit en dur. Le shellHook conserve la même syntaxe, ce qui rend la migration mécanique sur la plupart des projets.

{
description = "Projet migre de shell.nix vers flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.11";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [
python312
python312Packages.requests
git
curl
];
shellHook = ''
echo "Environnement dev (flake, nixos-24.11)"
'';
};
};
}
Aspectshell.nixflake.nix
Source nixpkgs<nixpkgs> (channel local)github:nixos/nixpkgs/nixos-24.11 (verrouillé)
Versions obtenuesVarient selon la machineIdentiques partout
Commande d'entréenix-shellnix develop
VerrouillageAucun (ou fetchTarball manuel)Automatique (flake.lock)

Le résultat avec nixos-24.11 verrouillé :

Fenêtre de terminal
nix develop --command bash -c "python3 --version && git --version"
Python 3.12.8
git version 2.47.2

Tandis qu'avec nixos-unstable, les mêmes paquets donnent Python 3.12.13 et git 2.53.0.

Par défaut, un flake cible un seul system (typiquement "x86_64-linux"). Pour supporter Linux et macOS sur x86_64 et ARM, utilisez flake-utils :

{
description = "Flake multi-architecture";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.default = pkgs.mkShell {
packages = [ pkgs.jq pkgs.curl ];
};
packages.default = pkgs.hello;
}
);
}

eachDefaultSystem itère automatiquement sur les quatre systèmes standards :

Fenêtre de terminal
nix flake show
├───devShells
│ ├───aarch64-darwin
│ │ └───default omitted (use '--all-systems' to show)
│ ├───aarch64-linux
│ │ └───default omitted (use '--all-systems' to show)
│ ├───x86_64-darwin
│ │ └───default omitted (use '--all-systems' to show)
│ └───x86_64-linux
│ └───default: development environment 'nix-shell'
└───packages
├───aarch64-darwin
│ └───default omitted (use '--all-systems' to show)
├───aarch64-linux
│ └───default omitted (use '--all-systems' to show)
├───x86_64-darwin
│ └───default omitted (use '--all-systems' to show)
└───x86_64-linux
└───default: package 'hello-2.12.3'

Si vous préférez éviter la dépendance à flake-utils, utilisez genAttrs de nixpkgs :

outputs = { self, nixpkgs }:
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" ];
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
pkgsFor = system: nixpkgs.legacyPackages.${system};
in {
packages = forAllSystems (system: {
default = (pkgsFor system).hello;
});
devShells = forAllSystems (system: {
default = (pkgsFor system).mkShell {
packages = [ (pkgsFor system).hello ];
};
});
};

direnv active automatiquement l'environnement du flake quand vous entrez dans le répertoire du projet. Plus besoin de taper nix develop manuellement.

  1. Installez direnv et nix-direnv :

    Fenêtre de terminal
    nix profile add nixpkgs#direnv nixpkgs#nix-direnv

    Sur une installation antérieure à Nix 2.30, la sous-commande s'appelle nix profile install. Elle reste acceptée comme alias déprécié.

  2. Configurez votre shell (ajoutez à ~/.bashrc ou ~/.zshrc) :

    Fenêtre de terminal
    eval "$(direnv hook bash)" # ou hook zsh
    source $HOME/.nix-profile/share/nix-direnv/direnvrc
  3. Créez un fichier .envrc à la racine du projet :

    Fenêtre de terminal
    echo "use flake" > .envrc
    direnv allow
  4. À chaque cd dans le projet, l'environnement se charge automatiquement.

Pour un projet réel, organisez vos fichiers Nix dans un sous-répertoire :

  • Répertoiremon-projet/
    • flake.nix Point d'entrée principal
    • flake.lock Versions verrouillées (généré)
    • .envrc Pour direnv (use flake)
    • Répertoirenix/
      • Répertoiredevshells/
        • default.nix Définition du devShell
      • Répertoirepackages/
        • default.nix Définitions de paquets
      • Répertoireoverlays/
        • default.nix Overlays personnalisés
    • Répertoiresrc/ Code source de l'application

Le flake.nix importe ensuite ces fichiers :

{
description = "Projet organisé";
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in {
devShells.${system}.default = import ./nix/devshells/default.nix { inherit pkgs; };
packages.${system}.default = import ./nix/packages/default.nix { inherit pkgs; };
};
}

Ce tableau résume ce que change le passage aux flakes sur un projet existant. Les deux lignes qui pèsent le plus dans une décision d'équipe sont Verrouillage et Évaluation : la première explique pourquoi la CI et le poste développeur finissent par diverger avec les channels, la seconde pourquoi un flake refuse de lire un fichier absent de Git. Les autres lignes décrivent du confort, celles-là décrivent des pannes que vous éviterez.

AspectChannels (classique)Flakes
VerrouillageManuel (fetchTarball + hash)Automatique (flake.lock)
ReproductibilitéDifficile à garantirGarantie par défaut
StructureLibre (convention par projet)Standardisée (inputs/outputs)
CompositionComplexe (imports manuels)Native (follows, multi-inputs)
ÉvaluationImpure (accès au système de fichiers)Pure (hermétique)
Commandesnix-env, nix-shell, nix-buildnix develop, nix build, nix run
Mise à journix-channel --update (global)nix flake update (par projet)
CI/CDConfiguration manuelleStructure prédictible

Quatre des six erreurs ci-dessous ont la même origine : l'évaluation pure. Un flake ne voit que les fichiers suivis par Git, un fichier créé mais jamais ajouté à l'index n'existe pas pour Nix, même s'il est bien sur le disque. Le réflexe à acquérir devant un message qui parle d'un chemin introuvable est donc git add, pas la recherche d'une erreur de syntaxe. L'avertissement Git tree is dirty fait exception : il est informatif, la construction aboutit quand même, il signale simplement que le résultat ne correspond à aucun commit et n'est donc pas reproductible ailleurs.

SymptômeCause probableSolution
error: experimental feature 'flakes' is disabledFlakes non activésAjouter experimental-features = nix-command flakes dans ~/.config/nix/nix.conf
error: getting status of '/flake.nix': No such file or directoryFichier non suivi par Gitgit add flake.nix
error: path '/nix/store/…' is not validFichier manquant dans Gitgit add <fichier-manquant>
warning: Git tree '…' is dirtyModifications non commitéesgit add -A && git commit (ou ignorer l'avertissement)
Build échoue en CI mais pas en localflake.lock absent ou pas à jourgit add flake.lock && git commit
--update-input affiche un warningSyntaxe dépréciéeUtiliser nix flake update <input>

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

6 questions
6 min.
70% requis

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

  • Un flake = flake.nix (déclaration) + flake.lock (verrouillage) dans un dépôt Git
  • Les inputs déclarent les sources ; follows évite la duplication
  • Les outputs produisent des devShells, packages, apps, avec une convention standardisée
  • nix develop remplace nix-shell, nix build remplace nix-build, nix run exécute directement
  • nix flake check valide la structure, indispensable en CI
  • nix flake update met à jour les inputs ; le diff de flake.lock sert de revue
  • flake-utils simplifie le support multi-architecture
  • direnv + nix-direnv activent l'environnement automatiquement

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn