Aller au contenu
Outils medium

direnv : variables d'environnement automatiques par projet

27 min de lecture

logo direnv

direnv charge et décharge automatiquement vos variables d’environnement quand vous entrez ou sortez d’un répertoire. Fini les export AWS_PROFILE=... à répéter dans chaque terminal. Avec un fichier .envrc à la racine de votre projet, direnv configure votre shell instantanément : variables, PATH, layouts Python/Node/Go, et même environnements Nix.

À la fin de ce guide, vous saurez configurer direnv pour automatiser la gestion de vos variables d’environnement par projet. Vous maîtriserez les fonctions de la stdlib comme PATH_add et dotenv, vous créerez des environnements Python et Node.js isolés avec les layouts, et vous appliquerez les bonnes pratiques de sécurité pour protéger vos secrets.

Imaginez que vous travaillez sur trois projets différents. Le premier utilise AWS avec le profil “client-a”, le second nécessite des variables d’API pour Stripe, et le troisième a besoin d’un virtualenv Python spécifique. Sans direnv, vous devez manuellement exporter ces variables à chaque fois que vous changez de projet, ou pire, les mettre dans votre .bashrc où elles polluent tous vos terminaux.

Avec direnv, chaque projet a son propre fichier .envrc qui s’active automatiquement quand vous entrez dans le répertoire. Quand vous en sortez, tout est nettoyé. C’est comme avoir un majordome qui ajuste l’éclairage, la température et la musique selon la pièce où vous entrez.

Voici ce qui se passe quand vous utilisez direnv :

Architecture direnv : hook shell, fichier .envrc, sub-shell et export

  1. Le hook s’active : À chaque fois que vous tapez cd, le hook direnv installé dans votre shell vérifie si un fichier .envrc existe dans le nouveau répertoire (ou ses parents).

  2. Vérification de sécurité : direnv ne charge pas automatiquement n’importe quel fichier .envrc. Vous devez d’abord l’autoriser avec direnv allow. Cette protection empêche l’exécution de code malveillant si vous clonez un dépôt compromis.

  3. Exécution dans un sub-shell : Le fichier .envrc est un script bash. Il s’exécute dans un sous-process isolé, pas dans votre shell actuel. direnv capture toutes les variables exportées.

  4. Export du diff : Seules les différences d’environnement sont injectées dans votre shell. Si vous aviez PATH=/usr/bin et que le script ajoute /mon-projet/bin, direnv exporte uniquement le nouveau PATH complet.

  5. Nettoyage à la sortie : Quand vous quittez le répertoire, direnv restaure l’environnement original. Les variables ajoutées disparaissent.

L’installation se fait en deux étapes : installer le binaire, puis configurer le hook dans votre shell.

  1. Installer avec mise

    La méthode recommandée utilise mise qui gère les versions de direnv comme n’importe quel autre outil :

    Fenêtre de terminal
    mise use -g direnv@latest

    Vérifiez l’installation :

    Fenêtre de terminal
    direnv --version
    2.37.1
  2. Configurer le hook shell

    Le hook doit être ajouté à la fin de votre fichier de configuration shell, après toute modification du PATH. C’est crucial car direnv doit s’exécuter après que votre environnement de base soit configuré.

    Pour Bash :

    Fenêtre de terminal
    echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
    source ~/.bashrc

    Pour Zsh :

    Fenêtre de terminal
    echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
    source ~/.zshrc
  3. Vérifier que le hook fonctionne

    Créez un projet de test pour confirmer que tout est en place :

    Fenêtre de terminal
    mkdir /tmp/test-direnv && cd /tmp/test-direnv
    echo 'export HELLO="direnv fonctionne"' > .envrc

    Vous devriez voir ce message :

    direnv: error /tmp/test-direnv/.envrc is blocked.
    Run `direnv allow` to approve its content

    C’est normal ! direnv vous demande d’autoriser le fichier avant de l’exécuter.

Mettons direnv en pratique avec un exemple concret. Nous allons créer un projet qui nécessite des variables d’environnement spécifiques.

  1. Créer le répertoire du projet

    Fenêtre de terminal
    mkdir ~/mon-projet && cd ~/mon-projet
  2. Créer le fichier .envrc

    Le fichier .envrc contient des commandes bash qui définissent vos variables :

    Fenêtre de terminal
    cat > .envrc << 'EOF'
    export PROJECT_NAME="demo-direnv"
    export DATABASE_URL="postgres://user:pass@localhost:5432/mydb"
    export DEBUG=true
    EOF
  3. Autoriser le fichier

    direnv refuse d’exécuter un .envrc non autorisé. C’est une mesure de sécurité importante :

    Fenêtre de terminal
    direnv allow
    direnv: loading ~/mon-projet/.envrc
    direnv: export +DATABASE_URL +DEBUG +PROJECT_NAME

    Le message +DATABASE_URL +DEBUG +PROJECT_NAME indique les variables qui ont été ajoutées à votre environnement.

  4. Vérifier que les variables sont chargées

    Fenêtre de terminal
    echo "Projet: $PROJECT_NAME"
    echo "Base de données: $DATABASE_URL"
    Projet: demo-direnv
    Base de données: postgres://user:pass@localhost:5432/mydb
  5. Observer le déchargement automatique

    Sortez du répertoire et vérifiez que les variables ont disparu :

    Fenêtre de terminal
    cd ..
    direnv: unloading
    Fenêtre de terminal
    echo "Projet: $PROJECT_NAME"
    Projet:

    La variable est vide car direnv a restauré votre environnement original.

Voici les commandes que vous utiliserez au quotidien. Chacune a un rôle précis dans votre workflow.

La commande direnv allow marque le fichier .envrc actuel comme sûr à exécuter. Sans cette autorisation, direnv ignore le fichier et affiche un avertissement.

Fenêtre de terminal
direnv allow
direnv: loading /tmp/direnv-demo/mon-projet/.envrc
direnv: export +DATABASE_URL +PROJECT_NAME

Vous devez réexécuter direnv allow chaque fois que vous modifiez le fichier .envrc. C’est une protection contre les modifications malveillantes.

Si vous ne voulez plus que direnv charge un .envrc (par exemple, après avoir cloné un dépôt suspect), utilisez :

Fenêtre de terminal
direnv deny

Le fichier sera ignoré jusqu’à ce que vous l’autorisiez à nouveau.

Quand vous modifiez votre .envrc, direnv détecte le changement et demande une nouvelle autorisation. Vous pouvez forcer le rechargement avec :

Fenêtre de terminal
direnv reload

Cette commande est utile quand direnv n’a pas détecté une modification (par exemple, si un fichier surveillé avec watch_file a changé).

La commande direnv edit ouvre le .envrc dans votre éditeur par défaut ($EDITOR) et l’autorise automatiquement quand vous sauvegardez :

Fenêtre de terminal
export EDITOR=vim # ou nano, code, etc.
direnv edit

C’est la méthode recommandée pour modifier votre .envrc car elle évite d’oublier le direnv allow.

Pour diagnostiquer des problèmes, utilisez direnv status qui affiche l’état complet de direnv dans le répertoire courant :

Fenêtre de terminal
direnv status
direnv exec path /home/bob/.local/share/mise/installs/direnv/2.37.1/direnv
DIRENV_CONFIG /home/bob/.config/direnv
bash_path /usr/bin/bash
disable_stdin false
warn_timeout 5s
whitelist.prefix []
whitelist.exact map[]
Loaded RC path /tmp/direnv-demo/mon-projet/.envrc
Loaded watch: ".envrc" - 2026-01-26T08:23:21+01:00
Loaded RC allowed 2
Found RC path /tmp/direnv-demo/mon-projet/.envrc

Cette sortie vous indique quel fichier .envrc est chargé, quand il a été modifié pour la dernière fois, et s’il est autorisé.

direnv fournit une bibliothèque standard de fonctions (stdlib) que vous pouvez utiliser dans vos fichiers .envrc. Ces fonctions simplifient les tâches courantes et évitent les erreurs classiques.

L’erreur la plus fréquente quand on manipule le PATH est d’écrire PATH=$PWD/bin au lieu de PATH=$PWD/bin:$PATH, ce qui écrase le PATH existant. La fonction PATH_add évite ce piège :

.envrc
PATH_add bin
PATH_add scripts

Voyons ce que ça donne en pratique. Créons un projet avec un script personnalisé :

mkdir -p ~/projet-scripts/bin
cat > ~/projet-scripts/bin/mon-script << 'EOF'
#!/bin/bash
echo "Script personnalisé exécuté !"
EOF
chmod +x ~/projet-scripts/bin/mon-script
echo 'PATH_add bin' > ~/projet-scripts/.envrc
cd ~/projet-scripts
direnv allow
direnv: loading ~/projet-scripts/.envrc
direnv: export ~PATH

Maintenant, le script est accessible directement :

Fenêtre de terminal
which mon-script
/home/bob/projet-scripts/bin/mon-script
Fenêtre de terminal
mon-script
Script personnalisé exécuté !

Le symbole ~PATH dans la sortie de direnv indique que le PATH a été modifié (pas ajouté avec +, mais modifié avec ~).

La fonction dotenv charge un fichier .env au format standard VARIABLE=valeur. C’est pratique pour séparer la logique (dans .envrc) des valeurs (dans .env) :

Fenêtre de terminal
mkdir ~/projet-dotenv && cd ~/projet-dotenv
# Créer un fichier .env
cat > .env << 'EOF'
API_KEY=sk-demo-123456
DATABASE_HOST=localhost
DEBUG=true
EOF
# Le .envrc charge simplement le .env
echo 'dotenv' > .envrc
direnv allow
direnv: loading ~/projet-dotenv/.envrc
direnv: export +API_KEY +DATABASE_HOST +DEBUG

Vérifiez que les variables sont chargées :

Fenêtre de terminal
echo "API_KEY: $API_KEY"
echo "DATABASE_HOST: $DATABASE_HOST"
echo "DEBUG: $DEBUG"
API_KEY: sk-demo-123456
DATABASE_HOST: localhost
DEBUG: true

Charger un fichier optionnel avec dotenv_if_exists

Section intitulée « Charger un fichier optionnel avec dotenv_if_exists »

Si le fichier .env n’existe pas toujours (par exemple, .env.local pour les surcharges locales), utilisez dotenv_if_exists qui ne génère pas d’erreur si le fichier est absent :

.envrc
# Configuration de base (versionnée)
export NODE_ENV=development
export API_URL=http://localhost:3000
# Surcharges locales (non versionnées)
dotenv_if_exists .env.local

Dans un monorepo ou un projet avec des sous-dossiers, vous pouvez hériter de la configuration du répertoire parent :

Fenêtre de terminal
# Créer une structure de projet
mkdir -p ~/monorepo/services/api
mkdir -p ~/monorepo/services/web
# Configuration racine
cat > ~/monorepo/.envrc << 'EOF'
export MONOREPO_ROOT=$PWD
export DOCKER_COMPOSE_PROJECT=monorepo
EOF
# Configuration du service API qui hérite de la racine
cat > ~/monorepo/services/api/.envrc << 'EOF'
source_up # Charge le .envrc parent
export SERVICE_NAME=api
export PORT=3001
EOF
cd ~/monorepo
direnv allow
cd services/api
direnv allow
Fenêtre de terminal
echo "Racine: $MONOREPO_ROOT"
echo "Service: $SERVICE_NAME"
echo "Port: $PORT"
Racine: /home/bob/monorepo
Service: api
Port: 3001

La fonction watch_file permet de recharger automatiquement l’environnement quand un fichier spécifique change. C’est utile quand votre .envrc génère des variables à partir de fichiers externes :

.envrc
# Recharger si requirements.txt change
watch_file requirements.txt
# Recharger si package.json change
watch_file package.json
# Extraire la version depuis package.json
if [[ -f package.json ]]; then
export APP_VERSION=$(jq -r '.version' package.json)
fi

La fonction has vérifie si une commande est disponible. C’est pratique pour adapter votre configuration selon les outils installés :

.envrc
# Charger la configuration Docker seulement si Docker est installé
if has docker; then
export DOCKER_HOST=unix:///var/run/docker.sock
export COMPOSE_PROJECT_NAME=mon-projet
fi
# Utiliser direnv seulement si la version est suffisante
direnv_version 2.30.0

Les layouts sont la fonctionnalité la plus puissante de direnv. Ils créent automatiquement des environnements isolés pour chaque langage, sans configuration manuelle.

Le layout Python crée un virtualenv dans .direnv/python-X.Y et l’active automatiquement. Plus besoin de python -m venv ou source venv/bin/activate :

Fenêtre de terminal
mkdir ~/projet-python && cd ~/projet-python
# Créer le .envrc avec le layout Python
echo 'layout python3' > .envrc
direnv allow
direnv: loading ~/projet-python/.envrc
direnv: export +VIRTUAL_ENV ~PATH

direnv a créé un virtualenv. Vérifions :

Fenêtre de terminal
ls -la .direnv/
total 16
drwxrwxr-x 3 bob bob 4096 janv. 26 08:23 .
drwxrwxr-x 3 bob bob 4096 janv. 26 08:23 ..
-rw-rw-r-- 1 bob bob 190 janv. 26 08:23 CACHEDIR.TAG
drwxrwxr-x 5 bob bob 4096 janv. 26 08:23 python-3.12

Le Python utilisé est maintenant celui du virtualenv :

Fenêtre de terminal
which python
/home/bob/projet-python/.direnv/python-3.12/bin/python
Fenêtre de terminal
python --version
Python 3.12.3

Installez des packages, ils iront dans le virtualenv :

Fenêtre de terminal
pip install requests
pip list

Les packages sont isolés dans ce projet. Quand vous sortez du répertoire, le virtualenv est désactivé automatiquement.

Le layout Node.js ajoute node_modules/.bin au PATH. Vous pouvez ainsi exécuter directement les binaires installés par npm sans préfixer par npx :

Fenêtre de terminal
mkdir ~/projet-node && cd ~/projet-node
npm init -y
npm install --save-dev eslint prettier
# Configurer direnv
echo 'layout node' > .envrc
direnv allow
direnv: loading ~/projet-node/.envrc
direnv: export ~PATH

Maintenant, eslint et prettier sont accessibles directement :

Fenêtre de terminal
which eslint
/home/bob/projet-node/node_modules/.bin/eslint
Fenêtre de terminal
eslint --version
v9.39.2

Sans le layout, vous auriez dû taper npx eslint ou ./node_modules/.bin/eslint.

Le layout Go configure un GOPATH local dans .direnv/go et ajoute bin/ au PATH :

.envrc
layout go

Effet :

  • GOPATH pointe vers .direnv/go
  • $PWD/bin est ajouté au PATH

Les binaires Go compilés avec go install iront dans .direnv/go/bin au lieu de ~/go/bin.

Le layout Ruby configure GEM_HOME pour installer les gems dans le projet :

.envrc
layout ruby

Effet : les gems s’installent dans .direnv/ruby/ au lieu du système. Plus besoin de préfixer par bundle exec.

direnv s’intègre nativement avec Nix pour créer des environnements de développement reproductibles. Si vous utilisez Nix, ces fonctions vous permettent de charger automatiquement votre environnement de développement.

La fonction use nix charge l’environnement défini dans shell.nix ou default.nix :

.envrc
use nix
shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.nodejs
pkgs.python3
pkgs.terraform
];
}

Si vous utilisez les Nix Flakes (fonctionnalité expérimentale), utilisez use flake :

.envrc
use flake
flake.nix
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs }: {
devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {
packages = with nixpkgs.legacyPackages.x86_64-linux; [
nodejs
python3
];
};
};
}

Voici des configurations .envrc prêtes à l’emploi pour différents types de projets.

.envrc
# Créer un virtualenv Python isolé
layout python3
# Variables Flask
export FLASK_APP=app.py
export FLASK_ENV=development
export FLASK_DEBUG=1
# Configuration de la base de données
export DATABASE_URL="sqlite:///dev.db"
# Charger les secrets locaux (non versionnés)
dotenv_if_exists .env.local
# Ajouter src/ au PYTHONPATH pour les imports
path_add PYTHONPATH src
# Recharger si les dépendances changent
watch_file requirements.txt
watch_file pyproject.toml
.envrc
# Binaires npm locaux
layout node
# Configuration Docker Compose
export COMPOSE_PROJECT_NAME=mon-api
export COMPOSE_FILE=docker-compose.yml:docker-compose.dev.yml
# Variables de développement
export NODE_ENV=development
export API_URL=http://localhost:3000
export LOG_LEVEL=debug
# Charger les variables privées
dotenv_if_exists .env
# Vérifier que Docker est disponible
if has docker; then
echo "Docker disponible : $(docker --version)"
fi
.envrc
# Profil AWS spécifique au projet
export AWS_PROFILE=client-production
export AWS_DEFAULT_REGION=eu-west-3
# Configuration Terraform
export TF_VAR_environment=production
export TF_VAR_project_name=infrastructure
# Backend S3 pour l'état Terraform
export TF_CLI_ARGS_init="-backend-config=environments/prod/backend.tfvars"
# Charger les variables sensibles (non versionnées)
dotenv_if_exists .env.terraform
# Vérifier que les outils sont installés
if ! has terraform; then
echo "⚠️ Terraform n'est pas installé"
fi
if ! has aws; then
echo "⚠️ AWS CLI n'est pas installé"
fi
.envrc
# Détecter l'environnement depuis .env.local ou variable
if [[ -f .env.local ]]; then
ENV=$(grep '^ENV=' .env.local | cut -d'=' -f2)
else
ENV=${ENV:-development}
fi
export ENV
# Configuration selon l'environnement
case $ENV in
development)
export API_URL=http://localhost:8080
export DEBUG=true
export LOG_LEVEL=debug
;;
staging)
export API_URL=https://staging.example.com
export DEBUG=false
export LOG_LEVEL=info
;;
production)
export API_URL=https://api.example.com
export DEBUG=false
export LOG_LEVEL=warn
;;
*)
echo "⚠️ Environnement inconnu: $ENV"
;;
esac
echo "🌍 Environnement: $ENV"
echo "🔗 API: $API_URL"

Le fichier .envrc est un script bash qui peut exécuter n’importe quelle commande. Cette puissance vient avec des responsabilités.

La meilleure pratique est de séparer la configuration (versionnée) des secrets (non versionnés) :

.envrc (versionné)
# Configuration publique
export NODE_ENV=development
export API_URL=http://localhost:3000
layout node
# Charger les secrets depuis un fichier non versionné
dotenv_if_exists .env.local
.env.local (non versionné)
# Secrets - Ne JAMAIS committer ce fichier
STRIPE_SECRET_KEY=sk_test_xxx
DATABASE_PASSWORD=motdepasse_secret
JWT_SECRET=cle_tres_secrete
.gitignore
# Fichiers de secrets
.env.local
.env.*.local
.envrc.private

Pour les projets professionnels, utilisez un gestionnaire de secrets comme sops ou HashiCorp Vault :

.envrc
# Charger les secrets depuis sops (fichier chiffré)
if has sops; then
eval "$(sops -d --output-type dotenv secrets.enc.yaml)"
fi

Depuis la version 2.38, direnv propose require_allowed pour forcer une nouvelle autorisation si certains fichiers changent. C’est utile pour se protéger contre les attaques via les lockfiles (npm, pixi) qui peuvent exécuter du code :

.envrc
# Exiger une nouvelle autorisation si ces fichiers changent
require_allowed package-lock.json
require_allowed pixi.lock

Vous pouvez personnaliser le comportement de direnv pour tous vos projets via le fichier de configuration ~/.config/direnv/direnv.toml.

~/.config/direnv/direnv.toml
[global]
# Charger automatiquement les fichiers .env (en plus de .envrc)
load_dotenv = true
# Cacher le diff d'environnement dans les messages
hide_env_diff = true
# Mode strict : échouer si une variable n'est pas définie
strict_env = false
[whitelist]
# Répertoires où direnv est automatiquement autorisé
# (sans besoin de `direnv allow`)
prefix = [
"~/Projets",
"~/work"
]

Vous pouvez étendre direnv avec vos propres fonctions en créant le fichier ~/.config/direnv/direnvrc :

~/.config/direnv/direnvrc
# Fonction pour charger un profil AWS
use_aws_profile() {
local profile=$1
export AWS_PROFILE=$profile
if has aws; then
if aws configure list-profiles 2>/dev/null | grep -q "^${profile}$"; then
log_status "Profil AWS: $profile"
else
log_error "Profil AWS '$profile' non trouvé"
return 1
fi
fi
}
# Fonction pour activer un environnement conda
layout_conda() {
local env_name=${1:-$(basename "$PWD")}
if has conda; then
eval "$(conda shell.bash hook)"
conda activate "$env_name" 2>/dev/null || {
log_status "Création de l'environnement conda: $env_name"
conda create -n "$env_name" python=3.12 -y
conda activate "$env_name"
}
fi
}

Utilisation dans un projet :

.envrc
use aws_profile client-production
layout conda mon-env-ml

Voici les problèmes les plus fréquents et leurs solutions.

Cause : Le fichier .envrc n’a pas été autorisé.

Solution :

Fenêtre de terminal
direnv allow

Cause : Le hook direnv n’est pas configuré dans votre shell.

Solution : Vérifiez que la ligne eval "$(direnv hook bash)" (ou zsh) est présente à la fin de votre .bashrc ou .zshrc, puis rechargez :

Fenêtre de terminal
source ~/.bashrc # ou ~/.zshrc

Cause : direnv n’est pas dans votre PATH.

Solution : Réinstallez direnv ou ajoutez son chemin au PATH. Avec mise :

Fenêtre de terminal
mise use -g direnv@latest

Les modifications du .envrc ne sont pas détectées

Section intitulée « Les modifications du .envrc ne sont pas détectées »

Cause : direnv attend une nouvelle autorisation.

Solution :

Fenêtre de terminal
direnv allow

Ou si vous avez modifié un fichier surveillé :

Fenêtre de terminal
direnv reload
Fenêtre de terminal
# État complet de direnv
direnv status
# Variables qui seraient exportées
direnv export bash
# Tester le .envrc sans l'appliquer
direnv exec . env | grep MA_VARIABLE

Voici les points essentiels à retenir pour utiliser direnv efficacement :

Commandes quotidiennes :

  • direnv allow autorise le .envrc actuel
  • direnv edit ouvre et autorise automatiquement
  • direnv reload force le rechargement

Fonctions stdlib utiles :

  • PATH_add bin ajoute un dossier au PATH en toute sécurité
  • dotenv ou dotenv_if_exists charge un fichier .env
  • layout python3 crée un virtualenv automatiquement
  • layout node ajoute node_modules/.bin au PATH
  • watch_file recharge quand un fichier change

Bonnes pratiques :

  • Séparez la configuration (versionnée) des secrets (non versionnés)
  • Utilisez dotenv_if_exists .env.local pour les surcharges locales
  • Ajoutez .env.local et .envrc.private à votre .gitignore

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.