J'ai eu la chance de présenter Mise à Devoxx France 2026 il y a quelques jours. J'utilise cet outil depuis plusieurs mois, et je trouvais intéressant de partager mon utilisation avec vous. Voici un retour d'expérience sur mon setup.

Si vous souhaitez une introduction plus complète vous pourrez bientôt voir la vidéo youtube, sinon les slides sont dispos ici.

Le problème : jongler entre contextes

Je suis consultant Cloud Native Infra, et je gère au quotidien plus d'une dizaine de projets avec chacun leurs clusters Kubernetes en dev et en prod, hébergés sur des zones différentes. Chaque projet a ses propres versions d'outils, ses variables d'environnement, ses secrets, ses scripts...

Au quotidien, ça donne grosso modo ça :

 1## Projet A - Dev - Helium ☕
 2$ java --version  # openjdk 25.x
 3$ python --version  # 3.14.x
 4$ kubectl version  # v1.35.x
 5$ export KUBECONFIG=~/.kube/helium
 6$ export ANSIBLE_INVENTORY=inventories/helium/dev
 7
 8## Projet B - Prod - Lithium ⚓
 9$ java --version  # openjdk 17.x
10$ python --version  # 3.11.x
11$ kubectl version  # v1.33.x
12$ export KUBECONFIG=~/.kube/lithium
13$ export ANSIBLE_INVENTORY=inventories/lithium/prod

Concrètement :

  • 🔄 Versions d'outils différentes selon les projets
  • 🌍 Variables d'environnement spécifiques à chaque contexte
  • 📜 Scripts custom à maintenir
  • ⚠️ Oublier de switcher = bugs, erreurs, frustration (😱 le terraform plan sur le mauvais projet qui demande de redéployer un cluster, c'est arrivé ^^)

Avant, je faisais tout cela avec un mix d'outils :

OutilCe qu'il fait
asdfGestion de versions d'outils
direnvVariables d'env par répertoire
sdkman / venv / nvmVersion manager (1 langage)
Scripts shellTout le reste (notamment source de secrets)

Ça marchait, mais c'était perfectible. Et puis j'ai (re)découvert Mise, un outil créé par @jdx en 2023, écrit en Rust (donc vachement rapide), qui agrège dans un seul binaire asdf + direnv + un task runner, avec une activation automatique par répertoire, le tout configurable simplement avec des fichiers au format TOML.

Pour l'installation, c'est un simple curl https://mise.run | sh puis eval "$(mise activate zsh)" dans le shell. Je ne vais pas refaire la doc ici, vous trouverez tout sur mise.jdx.dev, je préfère vous montrer mon vrai setup.

Mon setup : Un dizaine de projets & clusters Kubernetes

J'ai une arborescence du genre :

 1projects-kubernetes-clusters/
 2├── calcium/              # Projet A
 3│   ├── calcium-dev/
 4│   └── calcium-prod/
 5├── lithium/              # Projet B
 6│   ├── lithium-dev/
 7│   └── lithium-prod/
 8├── helium/               # Projet C
 9│   ├── helium-dev/
10│   │   └── ansible/
11│   └── helium-prod/
12└── ...                   # 10+ projets identiques

Et la magie de Mise, c'est que la configuration est hiérarchique sur 3 niveaux :

  1. Global (~/.config/mise/config.toml) : ce qui s'applique partout (proxy WireGuard par exemple)
  2. Projet parent (calcium/mise.toml) : variables OVH, endpoints, project name
  3. Environnement spécifique (calcium/calcium-dev/mise.toml) : nom du cluster, kubeconfig

Architecture 3 niveaux Mise

Niveau global

Au niveau de mon home, j'ai juste de quoi sourcer mon proxy WireGuard quand je suis chez un client :

1# ~/.config/mise.toml
2[env]
3# WireGuard Proxy
4_.source = ["~/.config/mise/proxy.sh"]

Niveau projet parent

Au niveau du projet parent, je définis les variables communes à tous les environnements (dev + prod), et j'active fnox pour les secrets :

 1# calcium/mise.toml
 2[env]
 3OS_REGION_NAME = "GRA7"
 4OVH_ENDPOINT = "ovh-eu"
 5OS_AUTH_URL = "https://auth.cloud.ovh.net/v3/"
 6
 7VAULT_ADDR = "https://vault.gravitek.io"
 8VAULT_OVH_PROJECT = "calcium"
 9TF_VAR_project_name = "{{env.VAULT_OVH_PROJECT}}"
10
11# fnox pour les secrets
12_.fnox-env = { tools = true }
13
14[plugins]
15fnox-env = "https://github.com/jdx/mise-env-fnox"

Les secrets sont gérés via fnox, l'outil compagnon de Mise. Chez moi, j'utilise principalement HashiCorp Vault pour ce qui est partagé. La conf tient dans un fnox.toml au niveau du projet :

 1# calcium/fnox.toml
 2[providers.vault-ovh-project-api]
 3type = "vault"
 4address = "https://vault.gravitek.io"
 5path = "secret/myorg/ovh/calcium"
 6
 7[providers.vault-ovh-project-openstack]
 8type = "vault"
 9address = "https://vault.gravitek.io"
10path = "secret/myorg/ovh/calcium/openstack"
11
12[secrets]
13OVH_APPLICATION_KEY = { provider = "vault-ovh-project-api", value = "API_CREDENTIALS/OVH_APPLICATION_KEY" }
14OVH_APPLICATION_SECRET = { provider = "vault-ovh-project-api", value = "API_CREDENTIALS/OVH_APPLICATION_SECRET" }
15OS_USERNAME = { provider = "vault-ovh-project-openstack", value = "admin/OS_USERNAME" }
16OS_PASSWORD = { provider = "vault-ovh-project-openstack", value = "admin/OS_PASSWORD" }

Et pour le VAULT_TOKEN lui-même, je le stocke dans le Keychain macOS, ce qui m'évite de l'avoir en clair quelque part :

1[providers.keychain]
2type = "keychain"
3service = "fnox"
4
5[secrets]
6VAULT_TOKEN = { provider = "keychain", value = "VAULT_TOKEN" }

Niveau environnement spécifique

Au niveau de l'environnement spécifique, j'ajoute le nom du cluster, l'URL, et surtout un hook qui auto-switch le contexte kubectl à l'entrée du répertoire :

 1# calcium/calcium-dev/mise.toml
 2[env]
 3CLUSTER_NAME = "calcium-dev"
 4CLUSTER_URL = "https://xxx.k8s.ovh.net"
 5
 6[hooks]   # mode expérimental
 7enter = """
 8if [ -n "$CLUSTER_NAME" ] && kubectl config get-contexts "$CLUSTER_NAME" &>/dev/null; then
 9  kubectl config use-context "$CLUSTER_NAME"
10fi
11"""

Plus de kubectx à la main, plus de risque de terraform plan sur le mauvais cluster 🙏.

Bonus : sous-répertoire Ansible avec venv Python

Pour mes répertoires Ansible, j'ajoute encore un niveau de configuration avec un venv Python dédié grâce à uv, et des tâches pour installer les dépendances :

 1# helium/helium-dev/ansible/mise.toml
 2[tools]
 3python = "3.14"
 4
 5[env]
 6_.python.venv = { path = ".venv", create = true, python = "3.14", uv_create_args = ['--seed'] }
 7
 8[tasks.install]
 9description = "Installer les dépendances et les rôles Ansible"
10run = ["uv pip install ansible ansible-lint python-gitlab",
11       "ansible-galaxy install -r roles/requirements.yml -p roles --force"]

Le résultat au quotidien

Concrètement, en switchant de répertoire :

 1$ cd projects/calcium/calcium-dev/
 2🔑 [MISE] Environnement CALCIUM détecté.
 3$ echo $PROJECT_NAME
 4calcium
 5$ kubectl config current-context
 6calcium-dev
 7$ echo $OVH_APPLICATION_SECRET
 8s3cr3t_p4ssw0rd      # résolu depuis Vault automatiquement
 9
10$ cd ../../lithium/lithium-prod/
11🔑 [MISE] Environnement LITHIUM détecté.
12$ echo $PROJECT_NAME
13lithium
14$ kubectl config current-context
15lithium-prod

Je cd dans un répertoire, tout est automatiquement chargé : versions d'outils, variables d'env, secrets résolus depuis Vault ou le Keychain, contexte kubectl switché. Zéro friction, zéro oubli.

Context switch entre projets avec Mise

Au final, Mise a remplacé chez moi : asdf, direnv, mes scripts shell custom, et même les virtualenvs python. C'est devenu le point d'entrée de mes projets.

Ressources