Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Cas pratiques DevOps avec Nix : standardiser les environnements et fiabiliser la CI/CD

Par mis à jour le

Améliorer cette page

Vous avez des flakes, des devShells, un flake.lock qui verrouille chaque dépendance. Maintenant, comment en faire un usage d'équipe concret ? Ce guide transforme les briques des guides 8 à 10 en six cas pratiques opérationnels : poste de travail standardisé, CI/CD identique au local, checks automatisés, build reproductible d'un outil interne, stratégie de mise à jour et onboarding express. Chaque exemple s'appuie sur la même flake.nix de référence, rejouée le 1er octobre 2026 avec Nix 2.35.2 et nixos-26.05, la branche stable courante.

  • Construire un devShell d'équipe embarquant Terraform, Ansible, kubectl, Helm et les outils de qualité
  • Consommer ce devShell dans GitHub Actions et GitLab CI, sans configuration supplémentaire
  • Définir des checks reproductibles (lint, format) exécutés localement et en CI avec nix flake check
  • Empaqueter un script d'administration comme dérivation Nix reproductible avec writeShellApplication
  • Gérer les mises à jour de flake.lock sans surprises pour l'équipe
  • Ramener l'onboarding d'un nouveau membre à git clone + nix develop

Nix devient particulièrement utile dès qu'une équipe est impliquée, parce que chaque poste et chaque runner de CI dérive de son côté. Les situations suivantes en sont les symptômes typiques :

  • Deux ingénieurs ont des versions différentes de Terraform ou d'Ansible, les plans divergent
  • La CI installe des paquets avec sudo apt install dans le pipeline, créant des builds fragiles et lents
  • Un nouveau contributeur passe une demi-journée à installer l'outillage avant d'écrire la première ligne
  • Une mise à jour silencieuse de helm en CI casse le déploiement, impossible de reproduire localement
  • Les scripts shell sont valides "sur ma machine" mais pas sur le runner

Un flake.nix versionné avec le code résout structurellement chacun de ces problèmes.

Ce guide fait suite aux guides 8 (environnements), 9 (factorisation) et 10 (flakes). Vous devez :

  • avoir Nix installé avec les flakes activés (voir le guide d'installation),
  • comprendre la structure d'un flake.nix (inputs, outputs, devShells),
  • savoir utiliser nix develop et nix flake check.

Créez ce projet dans votre répertoire de lab. Tous les cas pratiques utilisent cette même base :

Fenêtre de terminal
mkdir -p ~/Projets/lab-nix && cd ~/Projets/lab-nix
mkdir -p nix scripts src
git init
  • Répertoirelab-nix/
    • flake.nix (point d'entrée, inputs + délégation aux modules)
    • flake.lock (révisions verrouillées, toujours commité dans Git)
    • Répertoirenix/
      • devshells.nix (devShell d'équipe)
      • checks.nix (vérifications automatisables)
      • packages.nix (builds reproductibles)
    • Répertoirescripts/
      • lint.sh (lint shell)
      • validate.sh (validation Terraform)
    • Répertoiresrc/ (code source)
      • …
    • .envrc (activation automatique avec direnv, optionnel)

Le flake.nix racine reste court grâce à la factorisation dans nix/. C'est la convention introduite dans le guide 9. Deux détails comptent : Terraform est publié sous licence BSL depuis la version 1.6, et nixpkgs refuse de l'évaluer tant qu'on ne l'autorise pas explicitement ; et le fichier est écrit au format de nixfmt, sans quoi le check de format du cas pratique 3 échoue sur lui.

flake.nix
{
description = "Boîte à outils DevOps : reproductible pour toute l'équipe";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfreePredicate = pkg: builtins.elem (nixpkgs.lib.getName pkg) [ "terraform" ];
};
src = ./.;
in
{
devShells.default = import ./nix/devshells.nix { inherit pkgs; };
checks = import ./nix/checks.nix { inherit pkgs src; };
packages = import ./nix/packages.nix { inherit pkgs; };
}
);
}

Sans le bloc allowUnfreePredicate, pkgs = nixpkgs.legacyPackages.${system} suffit pour tout le reste, mais l'entrée dans le shell échoue :

error: Refusing to evaluate package 'terraform-1.15.3' in /nix/store/…/pkgs/applications/networking/cluster/terraform/default.nix:85 because it has an unfree license (‘bsl11’)

Cas pratique 1, Boîte à outils DevOps standardisée

Section intitulée « Cas pratique 1, Boîte à outils DevOps standardisée »

Un devShell unique qui embarque tout l'outillage d'une équipe DevOps. Chaque membre entre dans cet environnement avec nix develop et obtient les mêmes versions, peu importe sa distribution ou son historique d'installation. Attention au nom des attributs : dans nixpkgs, helm désigne un synthétiseur audio, le gestionnaire de paquets Kubernetes s'appelle kubernetes-helm.

nix/devshells.nix
{ pkgs }:
pkgs.mkShell {
name = "devops-toolkit";
packages = with pkgs; [
# Contrôle de version
git
git-crypt
# Traitement de données structurées
jq
yq-go
# Qualité de code shell
shellcheck
shfmt
# Infrastructure as Code
terraform
opentofu
# Gestion de configuration
ansible
ansible-lint
# Kubernetes
kubectl
kubernetes-helm
kustomize
k9s
# Sécurité
trivy
cosign
# Debug réseau
curl
httpie
];
shellHook = ''
echo "Environnement DevOps actif ($(uname -m))"
echo " Terraform : $(terraform version 2>/dev/null | head -1)"
echo " kubectl : $(kubectl version --client 2>/dev/null | head -1)"
echo " Helm : $(helm version --short 2>/dev/null)"
echo " Ansible : $(ansible --version 2>/dev/null | head -1)"
'';
}

Ajoutez les deux fichiers à l'index Git, puis entrez dans le shell : le shellHook affiche les versions fournies par nixos-26.05.

Fenêtre de terminal
cd ~/Projets/lab-nix
git add flake.nix nix/devshells.nix
nix develop
Environnement DevOps actif (x86_64)
Terraform : Terraform v1.15.3
kubectl : Client Version: v1.36.3
Helm : v3.20.2+gv3.20.2
Ansible : ansible [core 2.21.1]

Votre flake.nix est versionné avec le code. La CI le consomme directement avec la même commande qu'en local :

Fenêtre de terminal
# En local
nix develop --command bash -c "shellcheck scripts/*.sh && terraform -chdir=infra validate"
# En CI : exactement la même commande
nix develop --command bash -c "shellcheck scripts/*.sh && terraform -chdir=infra validate"

Les trois onglets suivants déclinent ce principe pour GitHub Actions, GitLab CI et un runner auto-hébergé. Les actions GitHub sont épinglées sur le SHA de leur commit, avec la version en commentaire, et le workflow ne reçoit que la permission de lire le dépôt.

.github/workflows/ci.yml
name: CI
on: [push, pull_request]
permissions: {}
jobs:
validate:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
extra_nix_config: |
experimental-features = nix-command flakes
# Cache binaire : réduit drastiquement le temps de téléchargement
- uses: cachix/cachix-action@38b082610b782e7e93e209c35fd730d399dee866 # v17
with:
name: my-org-cache
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- name: Vérification de la structure du flake
run: nix flake check --no-build
- name: Lint et validation IaC
run: |
nix develop --command bash -c "
shellcheck scripts/*.sh
terraform -chdir=infra validate
ansible-lint playbooks/ || true
"

Cas pratique 3, Checks reproductibles avec nix flake check

Section intitulée « Cas pratique 3, Checks reproductibles avec nix flake check »

Définir des vérifications (lint, format) comme des dérivations Nix : le check devient un paquet comme un autre, construit dans le bac à sable de Nix.

  • exécutables en local avec nix flake check,
  • intégrées automatiquement en CI,
  • mises en cache, un check déjà réussi ne tourne pas une deuxième fois.

Chaque check est un runCommandLocal qui lance l'outil puis crée $out : si l'outil échoue, la dérivation échoue, et nix flake check avec elle. Le paquet nixfmt est le formateur officiel de Nix ; l'ancien nom nixfmt-rfc-style n'est plus qu'un alias.

nix/checks.nix
{ pkgs, src }:
{
# Vérification des scripts shell
shellcheck = pkgs.runCommandLocal "check-shellcheck" { buildInputs = [ pkgs.shellcheck ]; } ''
find ${src}/scripts -name "*.sh" -exec shellcheck {} +
touch $out
'';
# Format des fichiers Nix (RFC-style)
nixfmt = pkgs.runCommandLocal "check-nixfmt" { buildInputs = [ pkgs.nixfmt ]; } ''
nixfmt --check ${src}/flake.nix
find ${src}/nix -name "*.nix" -exec nixfmt --check {} +
touch $out
'';
}

Lancez tous les checks d'un coup, ou un seul quand vous déboguez : nix build sur l'attribut du check affiche ses journaux en cas d'échec.

Fenêtre de terminal
cd ~/Projets/lab-nix
git add nix/checks.nix scripts/
# Tous les checks
nix flake check
# Un seul check, avec trace détaillée si erreur
nix build .#checks.x86_64-linux.shellcheck --show-trace
Résultat (fin de sortie)
running 2 flake checks...
building '/nix/store/cfk8z97ymsxb5wiiw5qawd5pd53j400q-check-nixfmt.drv'...
building '/nix/store/z09h1az790v0m84jsyd5ivcn2w9ksyig-check-shellcheck.drv'...
all checks passed!
warning: The check omitted these incompatible systems: aarch64-darwin, aarch64-linux, x86_64-darwin
Use '--all-systems' to check all.

Si shellcheck détecte un problème, le check échoue et Nix affiche la fin du journal :

Last 10 log lines:
>
> In /nix/store/…-source/scripts/lint.sh line 3:
> echo $x
> ^-- SC2086 (info): Double quote to prevent globbing and word splitting.
>
> Did you mean:
> echo "$x"

Le check de format se comporte de la même façon : un fichier Nix mal formaté sort en flake.nix: not formatted. Lancez nixfmt flake.nix nix/*.nix pour le corriger avant de commiter.

En GitLab CI et GitHub Actions, remplacez vos scripts de lint ad hoc par :

Fenêtre de terminal
nix flake check

C'est une seule commande, indépendante du runner, qui exécute exactement les mêmes vérifications qu'en local.


Cas pratique 4, Build reproductible d'un outil interne

Section intitulée « Cas pratique 4, Build reproductible d'un outil interne »

Empaqueter un script d'administration comme dérivation Nix. Le résultat est construit à partir des mêmes entrées sur toutes les machines, donc au même chemin du store, et peut être distribué via un cache binaire ou installé directement avec nix profile add.

Le script ne suppose rien du poste qui l'exécute : kubectl et jq viennent du store, à la version fixée par le flake.lock, et writeShellApplication refuse de construire un script que shellcheck juge fautif.

nix/packages.nix
{ pkgs }:
{
# Outil d'audit Kubernetes avec ses dépendances exactes embarquées
k8s-audit = pkgs.writeShellApplication {
name = "k8s-audit";
runtimeInputs = with pkgs; [
kubectl
jq
];
text = ''
echo "=== Nœuds du cluster ==="
kubectl get nodes -o json \
| jq -r '.items[] | "\(.metadata.name)\t\(.status.conditions[]
| select(.type=="Ready") | .status)"'
echo ""
echo "=== Pods non Running ==="
kubectl get pods --all-namespaces \
--field-selector 'status.phase!=Running,status.phase!=Succeeded' \
2>/dev/null || echo "(aucun)"
echo ""
echo "=== Événements Warning récents ==="
kubectl get events --all-namespaces \
--field-selector type=Warning \
--sort-by='.lastTimestamp' \
2>/dev/null | tail -15
'';
};
}

Les quatre commandes couvrent le cycle complet : construire l'outil, l'exécuter depuis ./result, l'essayer sans l'installer, puis l'ajouter au profil.

Fenêtre de terminal
cd ~/Projets/lab-nix
git add nix/packages.nix
# Construire
nix build .#k8s-audit
# Tester (le binaire est dans ./result/bin/)
./result/bin/k8s-audit
# Utiliser temporairement sans installer
nix shell .#k8s-audit --command k8s-audit
# Installer dans le profil utilisateur
nix profile add .#k8s-audit

Le binaire k8s-audit utilise ses propres versions de kubectl et jq depuis le store Nix : il fonctionne sur n'importe quelle machine avec Nix installé, sans dépendance système.


Cas pratique 5, Stratégie de mise à jour de flake.lock

Section intitulée « Cas pratique 5, Stratégie de mise à jour de flake.lock »

flake.lock fige la révision de nixpkgs. Il doit être mis à jour régulièrement pour obtenir les correctifs de sécurité et les nouvelles versions d'outils, sans casser l'équipe au passage.

Les six étapes isolent la mise à jour dans une branche, la font valider par la CI, et gardent un chemin de retour arrière.

  1. Créer une branche dédiée

    Fenêtre de terminal
    git checkout -b chore/nix-update-$(date +%Y-%m)
  2. Mettre à jour toutes les dépendances

    Fenêtre de terminal
    nix flake update

    Cela recrée flake.lock avec les révisions les plus récentes de chaque input déclaré.

  3. Inspecter ce qui a changé

    Fenêtre de terminal
    git diff flake.lock
    "rev": "<ancienne révision, 40 caractères>",
    "rev": "78e9c786dc08cd4f3420c2395cd977206a9b1da2",

    La révision de nixpkgs est la seule vraie information. Consultez github.com/NixOS/nixpkgs/compare/<ancienne-rev>...<nouvelle-rev> pour voir les changements inclus.

  4. Valider localement

    Fenêtre de terminal
    nix flake check
    nix develop --command bash -c "terraform version && kubectl version --client"
  5. Ouvrir une PR/MR

    La CI valide les checks automatiquement. La PR est fusionnée si tout est vert.

  6. Rollback si problème

    Fenêtre de terminal
    # Revenir simplement à la révision précédente de nixpkgs
    git revert HEAD
    # Ou épingler une révision spécifique
    nix flake lock --override-input nixpkgs \
    github:nixos/nixpkgs/<révision-de-40-caractères>

La bonne fréquence dépend du rythme de l'équipe et de son exposition : plus les dépendances sont nombreuses, plus une mise à jour tardive ramène de changements d'un coup.

FréquenceCas d'usage
HebdomadaireÉquipes avec forte activité CI/CD, dépendances nombreuses
MensuelleProjets stables, outillage peu changeant
À la demandeCVE critique dans une dépendance détectée par la veille

Un nouveau DevOps rejoint l'équipe. En moins de 15 minutes, il dispose de l'environnement complet, aux mêmes versions que ses collègues.

  1. Installer Nix (une seule fois, 3-5 minutes)

    Fenêtre de terminal
    curl -fsSL -o install-nix.sh https://releases.nixos.org/nix/nix-2.35.2/install
    echo "$(curl -fsSL https://releases.nixos.org/nix/nix-2.35.2/install.sha256) install-nix.sh" \
    | sha256sum --check
    sh install-nix.sh --no-daemon
    source ~/.nix-profile/etc/profile.d/nix.sh
  2. Activer les flakes

    Fenêtre de terminal
    mkdir -p ~/.config/nix
    echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
  3. Cloner et entrer dans l'environnement

    Fenêtre de terminal
    git clone https://gitlab.mon-org.example.com/infra/devops-tools.git
    cd devops-tools
    nix develop
  4. Vérifier

    Fenêtre de terminal
    terraform version
    kubectl version --client
    helm version
    ansible --version

    Toutes les versions affichées sont identiques à celles du reste de l'équipe.

  5. (Optionnel) Activation automatique avec direnv

    Fenêtre de terminal
    nix profile add nixpkgs#direnv
    echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
    source ~/.bashrc
    direnv allow .

    À partir de là, l'environnement du flake se charge automatiquement à chaque cd dans le répertoire, à condition que le dépôt contienne un .envrc avec use flake.

Le README d'installation se réduit à ces 5 étapes. Plus de liste de paquets à maintenir, plus de "ça marche chez moi" : la source de vérité est le flake.


Voici comment les six cas pratiques se combinent dans un projet réaliste. Le shell ci embarque tout ce que le pipeline appelle, ansible-lint compris : un outil absent du shell ferait échouer le job avec command not found. Le fichier est déjà au format de nixfmt, que son propre check vérifie.

flake.nix (version complète)
{
description = "Infra : Python + Terraform + Ansible";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
# Terraform est sous licence BSL : nixpkgs refuse de l'évaluer sans autorisation explicite
pkgs = import nixpkgs {
inherit system;
config.allowUnfreePredicate = pkg: builtins.elem (nixpkgs.lib.getName pkg) [ "terraform" ];
};
src = ./.;
in
{
# Cas pratique 1 : plusieurs shells selon le rôle
devShells = {
default = pkgs.mkShell {
name = "infra-full";
packages = with pkgs; [
python312
python312Packages.boto3
terraform
ansible
ansible-lint
kubectl
kubernetes-helm
shellcheck
shfmt
];
shellHook = ''
echo "Environnement complet actif"
'';
};
ci = pkgs.mkShell {
# Shell allégé pour la CI : sans éléments interactifs
name = "infra-ci";
packages = with pkgs; [
python312
terraform
ansible
ansible-lint
kubectl
shellcheck
];
};
};
# Cas pratique 3 : checks automatisés
checks = {
shellcheck = pkgs.runCommandLocal "check-shellcheck" { buildInputs = [ pkgs.shellcheck ]; } ''
find ${src}/scripts -name "*.sh" -exec shellcheck {} +
touch $out
'';
nixfmt = pkgs.runCommandLocal "check-nixfmt" { buildInputs = [ pkgs.nixfmt ]; } ''
nixfmt --check ${src}/flake.nix
touch $out
'';
};
# Cas pratique 4 : outil interne empaquetable
packages.default = pkgs.writeShellApplication {
name = "infra-status";
runtimeInputs = with pkgs; [
kubectl
jq
python312
];
text = ''
echo "=== Cluster status ==="
kubectl get nodes -o json | jq -r '.items[].metadata.name'
'';
};
}
);
}

Le pipeline correspondant appelle le shell ci pour chaque outil, et construit l'outil interne exposé sous packages.default, d'où un nix build sans attribut :

.github/workflows/ci.yml (fil rouge)
name: Infra CI
on: [push, pull_request]
permissions: {}
jobs:
checks:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1
with:
github_access_token: ${{ secrets.GITHUB_TOKEN }}
extra_nix_config: |
experimental-features = nix-command flakes
- run: nix flake check
- run: nix develop .#ci --command terraform -chdir=infra validate
- run: nix develop .#ci --command ansible-lint playbooks/
- run: nix build

Ces pièges ne relèvent pas de la syntaxe Nix mais de son usage en équipe : ils passent inaperçus sur un poste isolé et se révèlent en CI ou au premier changement de version.

PiègeImpactCorrectif
git add oublié avant nix developNix refuse le fichier : Path '…' is not tracked by Gitgit add -A systématique avant toute commande Nix
flake.lock absent du dépôtChaque machine ou CI job résout une révision différenteCommiter flake.lock, c'est une fonctionnalité, pas un artefact jetable
Terraform sans autorisation de licenceRefusing to evaluate package 'terraform-…' because it has an unfree licenseallowUnfreePredicate limité à terraform, ou passer à opentofu
helm au lieu de kubernetes-helmUn synthétiseur audio s'installe, helm version ne répond pasUtiliser l'attribut kubernetes-helm
Cache Nix absent en CIPremier job lent (plusieurs minutes de téléchargements)Configurer Cachix ou un runner persistant avec /nix monté
Trop d'outils dans un seul devShellTemps d'entrée long, résolution de conflits plus probablePlusieurs devShells nommés selon le rôle (default, sec, ci)
shellHook lourdRalentit chaque nix developGarder le shellHook court, déléguer aux scripts
Ignorer nix flake check en PRErreurs de structure détectées trop tardAjouter nix flake check --no-build comme premier job
Mettre à jour flake.lock sans branche dédiéeImpossible de revenir en arrière proprementToujours une branche + PR dédiée pour les mises à jour

  • La flake devient le contrat entre le poste de développement, la CI et les collègues, une seule source de vérité pour toute l'équipe
  • nix develop --command ... en CI consomme exactement le même environnement qu'en local, plus de divergence
  • flake.lock doit être versionné dans Git, il garantit que tout le monde et la CI travaillent avec les mêmes révisions
  • Les checks Nix sont mis en cache : un check déjà réussi ne tourne pas une deuxième fois
  • writeShellApplication est le moyen propre d'empaqueter des scripts avec leurs dépendances, placées en tête du PATH du seul script
  • L'onboarding se résume à : installer Nix une fois + git clone + nix develop
  • Mettez flake.lock à jour régulièrement via une PR dédiée, jamais directement sur la branche principale

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

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn