Cloud-init automatise la configuration de vos VMs cloud au premier démarrage. Vous définissez vos utilisateurs, clés SSH, paquets et scripts dans un fichier YAML appelé user-data, et cloud-init les applique sans intervention manuelle.
Ce guide vous apprend à créer un user-data fonctionnel, à le tester localement avec Incus, et à déboguer quand ça ne marche pas. Vous comprendrez aussi une distinction cruciale : user-data ≠ configuration réseau.
Ce que cloud-init fait (et ne fait pas)
Section intitulée « Ce que cloud-init fait (et ne fait pas) »Cloud-init est un outil d’initialisation, pas de gestion de configuration continue. Il s’exécute au premier démarrage de l’instance et ne se relance pas automatiquement ensuite — contrairement à Ansible ou Puppet qui peuvent tourner en boucle.
Concrètement, cloud-init peut :
- Créer des utilisateurs avec leurs clés SSH et droits sudo
- Installer des paquets via apt, yum, dnf ou zypper
- Écrire des fichiers de configuration sur le disque
- Exécuter des commandes à la fin de l’initialisation
- Configurer le hostname et d’autres paramètres système
En revanche, cloud-init ne peut pas modifier la configuration réseau via user-data dans la plupart des cas — c’est un point qui crée beaucoup de confusion.
Les 5 boot stages officiels
Section intitulée « Les 5 boot stages officiels »Cloud-init organise son exécution en 5 stages distincts. Comprendre cette séquence vous aidera à déboguer quand vos scripts ne s’exécutent pas au bon moment.
1. Detect (ds-identify)
Section intitulée « 1. Detect (ds-identify) »Ce premier stage détermine si cloud-init doit s’exécuter et quel datasource utiliser. Le script ds-identify cherche des indices (fichiers, métadonnées) pour identifier l’environnement cloud (AWS, GCP, Azure, OpenStack, NoCloud…).
Si aucun datasource n’est détecté, cloud-init peut être désactivé automatiquement. C’est pourquoi sur certaines images “non-cloud”, cloud-init ne fait rien.
2. Local (avant le réseau)
Section intitulée « 2. Local (avant le réseau) »Ce stage s’exécute avant que le réseau ne soit configuré. Cloud-init applique la configuration réseau (depuis le datasource, pas depuis user-data) et exécute les commandes bootcmd si présentes.
C’est ici que se joue la configuration des interfaces réseau. À la fin de ce stage, le réseau devrait être opérationnel.
3. Network (user-data disponible)
Section intitulée « 3. Network (user-data disponible) »Une fois le réseau actif, cloud-init récupère les données depuis le datasource : metadata, vendor-data et user-data. Les modules “cloud_init_modules” s’exécutent — ils préparent le terrain pour la configuration principale.
4. Config (la configuration principale)
Section intitulée « 4. Config (la configuration principale) »C’est le cœur de l’action. Cloud-init exécute les modules “cloud_config_modules” qui appliquent votre user-data :
- Création des utilisateurs et groupes
- Configuration SSH
- Installation des paquets (
packages:) - Écriture de fichiers (
write_files:)
5. Final (scripts utilisateur)
Section intitulée « 5. Final (scripts utilisateur) »Le dernier stage exécute les modules “cloud_final_modules”, notamment :
- runcmd : vos commandes personnalisées
- scripts-user : scripts dans
/var/lib/cloud/scripts/ - phone-home : notification vers un serveur externe
C’est seulement à ce stage que vos commandes runcmd s’exécutent — si vous attendez qu’un service soit configuré avant de lancer runcmd, c’est normal.
Anatomie d’un fichier user-data
Section intitulée « Anatomie d’un fichier user-data »Un fichier user-data commence obligatoirement par #cloud-config (sans espace) sur la première ligne. Le reste est du YAML standard.
#cloud-config
# Utilisateurs à créerusers: - name: deploy groups: sudo shell: /bin/bash ssh_authorized_keys: - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... sudo: ALL=(ALL) NOPASSWD:ALL
# Mise à jour des paquets existantspackage_update: truepackage_upgrade: true
# Paquets à installerpackages: - nginx - git - htop
# Fichiers à créerwrite_files: - path: /etc/motd content: | Bienvenue sur ce serveur configuré par cloud-init! permissions: '0644'
# Commandes à exécuter (stage Final)runcmd: - systemctl enable nginx - systemctl start nginxLes directives les plus utiles
Section intitulée « Les directives les plus utiles »Voici les directives que vous utiliserez dans 90% des cas :
- users crée des utilisateurs avec leurs clés SSH. Chaque utilisateur peut avoir
groups,shell,ssh_authorized_keysetsudo. - packages liste les paquets à installer. Cloud-init détecte automatiquement le gestionnaire de paquets (apt, yum, dnf…).
- package_update et package_upgrade mettent à jour la liste des paquets et les paquets installés avant d’en installer de nouveaux.
- write_files crée des fichiers avec un contenu spécifique. Utile pour déposer des configurations. runcmd exécute des commandes shell après que tout le reste soit configuré. Attention : ces commandes s’exécutent en tant que root.
Valider votre configuration avant déploiement
Section intitulée « Valider votre configuration avant déploiement »Ne déployez jamais un user-data sans l’avoir validé. Cloud-init fournit une commande de validation qui détecte les erreurs de syntaxe YAML et les directives inconnues.
Installer cloud-init localement
Section intitulée « Installer cloud-init localement »Pour valider vos fichiers user-data avant de les déployer, installez cloud-init avec pipx (installation isolée, sans polluer votre système) :
# Installer pipx si nécessaire# Linux: sudo apt install pipx && pipx ensurepath# macOS: brew install pipx && pipx ensurepath
pipx install cloud-initVérifiez l’installation :
cloud-init --versioncloud-init schema -c user-data.yaml --annotateLa commande cloud-init schema -c fichier.yaml --annotate fonctionne sans sudo
et ne nécessite pas que cloud-init soit “actif” sur le système. C’est parfait
pour valider vos fichiers depuis votre poste de développement. Cette installation avec
pipx permet de valider la syntaxe, mais pas d’exécuter cloud-init
complètement. Pour tester l’exécution réelle, utilisez Incus ou
Multipass (voir section “Tester avec Incus”).
Validation d’un fichier local
Section intitulée « Validation d’un fichier local »cloud-init schema -c user-data.yaml --annotateLe flag --annotate ajoute des commentaires utiles en cas d’erreur, indiquant exactement quelle ligne pose problème.
Si tout va bien :
Valid schema user-data.yamlSi erreur :
user-data.yaml:12: 'package' is not a valid keyword (did you mean 'packages'?)Validation sur une instance en cours d’exécution
Section intitulée « Validation sur une instance en cours d’exécution »Pour vérifier que l’instance a bien reçu et interprété votre user-data :
sudo cloud-init schema --system --annotateCette commande valide le user-data tel qu’il a été reçu par l’instance — utile pour déboguer les problèmes de transmission.
Tester avec Incus (sandbox locale)
Section intitulée « Tester avec Incus (sandbox locale) »Incus (fork de LXD) est parfait pour tester vos configurations cloud-init sans consommer de ressources cloud. Les conteneurs Incus avec images /cloud supportent nativement cloud-init.
Lister les images cloud disponibles
Section intitulée « Lister les images cloud disponibles »incus image list images: architecture=x86_64 /cloud -c lat | head -20Vous verrez des images comme ubuntu/noble/cloud, debian/bookworm/cloud, almalinux/9/cloud — ce sont celles qui incluent cloud-init.
Lancer un conteneur avec user-data
Section intitulée « Lancer un conteneur avec user-data »-
Créez votre fichier user-data
user-data.yaml #cloud-configusers:- name: testusergroups: sudoshell: /bin/bashssh_authorized_keys:- ssh-ed25519 AAAAC3...votre-clé...sudo: ALL=(ALL) NOPASSWD:ALLpackage_update: truepackages:- curl- vimruncmd:- touch /tmp/cloud-init-complete -
Lancez le conteneur avec le user-data
Fenêtre de terminal incus launch images:ubuntu/noble/cloud test-cloud-init \--config=cloud-init.user-data="$(cat user-data.yaml)"Notez l’espace entre
test-cloud-initet--config— c’est une erreur de syntaxe courante. -
Attendez que cloud-init termine
Fenêtre de terminal incus exec test-cloud-init -- cloud-init status --waitRésultat attendu :
status: done -
Vérifiez que tout est configuré
Fenêtre de terminal # L'utilisateur existe ?incus exec test-cloud-init -- id testuser# Les paquets sont installés ?incus exec test-cloud-init -- which curl# Le fichier témoin existe ?incus exec test-cloud-init -- ls /tmp/cloud-init-complete
Utiliser un profil Incus pour réutiliser la config
Section intitulée « Utiliser un profil Incus pour réutiliser la config »Si vous testez souvent, créez un profil plutôt que de passer le user-data à chaque fois :
# Créer le profilincus profile create cloud-init-test
# Éditer pour ajouter le user-dataincus profile edit cloud-init-testAjoutez dans le YAML du profil :
config: cloud-init.user-data: | #cloud-config users: - name: deploy groups: sudo shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL package_update: true packages: - nginxPuis lancez avec le profil :
incus launch images:ubuntu/noble/cloud mon-test \ --profile default --profile cloud-init-testAjouter une configuration réseau (NoCloud)
Section intitulée « Ajouter une configuration réseau (NoCloud) »Si vous avez besoin de configurer le réseau (IP statique par exemple), utilisez cloud-init.network-config :
incus config set mon-conteneur cloud-init.network-config - << 'EOF'version: 2ethernets: eth0: dhcp4: false addresses: - 10.0.0.50/24 gateway4: 10.0.0.1 nameservers: addresses: - 8.8.8.8EOFRecettes du quotidien
Section intitulée « Recettes du quotidien »Ces recettes couvrent les cas d'usage les plus fréquents. Cliquez sur un pattern pour voir la formule complète et un exemple prêt à copier.
Valider un fichier user-data Base Vérifier la syntaxe et les directives avant déploiement
cloud-init schema -c user-data.yaml --annotate
cloud-init schema -c FICHIER --annotate cloud-init schema -c user-data.yaml --annotate -
FICHIER— Chemin vers le fichier user-data
Vérifier l'état d'exécution Base Savoir si cloud-init a terminé et s'il y a des erreurs
cloud-init status --long
cloud-init status --long cloud-init status --long Consulter les logs Base Voir ce que cloud-init a fait et les erreurs
sudo grep -i error /var/log/cloud-init.log
sudo less /var/log/cloud-init.log sudo grep -i error /var/log/cloud-init.log Analyser les performances Base Identifier les modules lents
cloud-init analyze blame | head -10
cloud-init analyze blame cloud-init analyze blame | head -10 Forcer une ré-exécution Base Supprimer l'état pour rejouer cloud-init au prochain boot
sudo cloud-init clean --logs && sudo reboot
sudo cloud-init clean && sudo reboot sudo cloud-init clean --logs && sudo reboot Tester avec Incus Base Lancer un conteneur avec user-data pour test local
incus launch images:ubuntu/noble/cloud test --config=cloud-init.user-data="$(cat user-data.yaml)"
incus launch images:ubuntu/noble/cloud NOM --config=cloud-init.user-data="$(cat FICHIER)" incus launch images:ubuntu/noble/cloud test --config=cloud-init.user-data="$(cat user-data.yaml)" -
NOM— Nom du conteneur -
FICHIER— Fichier user-data
Attendre la fin de cloud-init Base Bloquer jusqu'à ce que cloud-init termine
incus exec test -- cloud-init status --wait
incus exec CONTENEUR -- cloud-init status --wait incus exec test -- cloud-init status --wait -
CONTENEUR— Nom du conteneur Incus
Rejouer un module spécifique Base Exécuter un seul module sans rebooter
sudo cloud-init single --name cc_runcmd
sudo cloud-init single --name MODULE sudo cloud-init single --name cc_runcmd -
MODULE— Nom du module (cc_runcmd, cc_write_files...)
Aucune recette ne correspond à votre recherche.
Pièges courants
Section intitulée « Pièges courants »Ces erreurs courantes peuvent faire perdre du temps ou causer des dégâts. Les pièges les plus critiques sont affichés en premier.
Mettre la config réseau dans user-data runcmd:\n - ip addr add 10.0.0.50/24 dev eth0
Danger
runcmd:\n - ip addr add 10.0.0.50/24 dev eth0
Secrets dans user-data write_files:\n - path: /etc/app/secrets.env\n content: API_KEY=sk-xxx
Danger
write_files:\n - path: /etc/app/secrets.env\n content: API_KEY=sk-xxx
Oublier #cloud-config users:\n - name: deploy
Attention
users:\n - name: deploy
#cloud-config\nusers:\n - name: deploy Attendre runcmd trop tôt Chercher un fichier créé par runcmd juste après le boot
Attention
Chercher un fichier créé par runcmd juste après le boot
Scripts non idempotents avec cloud-init clean runcmd:\n - useradd deploy
Attention
runcmd:\n - useradd deploy
users:\n - name: deploy\n groups: sudo Indentation YAML incorrecte packages:\n- nginx\n - git
Attention
packages:\n- nginx\n - git
Confondre package et packages package:\n - nginx
Attention
package:\n - nginx
packages:\n - nginx Déboguer quand ça ne marche pas
Section intitulée « Déboguer quand ça ne marche pas »Cloud-init génère des logs détaillés. Voici comment les exploiter efficacement.
Vérifier l’état global
Section intitulée « Vérifier l’état global »cloud-init status --longSortie typique :
status: doneextended_status: doneboot_status_code: enabled-by-generatorlast_update: Wed, 29 Jan 2026 10:15:42 +0000detail: DataSourceNoCloud [seed=/var/lib/cloud/seed/nocloud-net]errors: []recoverable_errors: {}Les champs importants :
- status :
running(en cours),done(terminé),error(échec) - detail : le datasource utilisé
- errors : liste des erreurs bloquantes
Consulter les logs
Section intitulée « Consulter les logs »Les deux fichiers de log principaux :
# Log détaillé de chaque actionsudo less /var/log/cloud-init.log
# Sortie des scripts (stdout/stderr)sudo less /var/log/cloud-init-output.logPour chercher des erreurs rapidement :
sudo grep -i error /var/log/cloud-init.logsudo grep -i warn /var/log/cloud-init.logAnalyser les performances
Section intitulée « Analyser les performances »Si cloud-init est lent, identifiez les étapes qui prennent du temps :
cloud-init analyze blameSortie exemple :
-- Boot Record 01 -- 45.23s (init-network/config-apt-configure) 12.45s (init-network/config-package-update-upgrade-install) 3.21s (modules-config/config-ssh)Ici, la mise à jour des paquets prend 45 secondes — c’est souvent le cas sur les premières exécutions.
Forcer une ré-exécution (debug uniquement)
Section intitulée « Forcer une ré-exécution (debug uniquement) »Cloud-init est conçu pour le first boot uniquement. Cependant, pour le debug, vous pouvez forcer une ré-exécution :
# Supprimer les sémaphores (cloud-init "oublie" qu'il a déjà tourné)sudo cloud-init clean
# Au prochain reboot, cloud-init rejouera toutsudo rebootVous pouvez aussi exécuter un module spécifique sans rebooter :
sudo cloud-init single --name cc_runcmdCas d’usage avancé : Ansible en mode pull
Section intitulée « Cas d’usage avancé : Ansible en mode pull »Cloud-init peut installer Ansible et exécuter un playbook depuis un dépôt Git via ansible-pull. C’est utile pour des configurations plus complexes que ce que cloud-init gère nativement.
#cloud-configusers: - name: ansible groups: sudo shell: /bin/bash sudo: ALL=(ALL) NOPASSWD:ALL
package_update: truepackages: - git - python3-pip
write_files: - path: /home/ansible/.config/pip/pip.conf content: | [global] break-system-packages = true owner: ansible:ansible
ansible: install_method: pip package_name: ansible run_user: ansible pull: url: https://github.com/votre-org/playbooks.git playbook_name: site.ymlCette configuration :
- Crée un utilisateur
ansibleavec sudo sans mot de passe - Installe Ansible via pip
- Clone le repo Git et exécute
site.yml
Comprendre vendor-data
Section intitulée « Comprendre vendor-data »En plus de user-data (que vous fournissez), cloud-init consomme deux autres sources de données :
metadata contient des informations sur l’instance (ID, région, adresse IP…) fournies par le provider cloud.
vendor-data est une configuration fournie par le provider cloud ou l’image. Elle s’applique avant votre user-data et peut installer des agents, configurer des services par défaut, etc.
Qui fournit quoi ?
Section intitulée « Qui fournit quoi ? »| Source | Fourni par | Exemple |
|---|---|---|
| metadata | Provider cloud | Instance ID, IP privée |
| vendor-data | Provider / image | Agent de monitoring, config réseau |
| user-data | Vous | Users, packages, scripts |
| network-config | Provider / datasource | IP statique, routes |
Désactiver des parties de vendor-data
Section intitulée « Désactiver des parties de vendor-data »Si le vendor-data du provider interfère avec votre config, vous pouvez le filtrer :
#cloud-configvendordata: excluded: - text/part-handlerCe qu’il faut retenir
Section intitulée « Ce qu’il faut retenir »Cloud-init est puissant mais a des règles précises. Gardez ces points en tête :
Le réseau n’est pas dans user-data. C’est la confusion #1. La config réseau vient du datasource ou de network-config séparé.
Cloud-init s’exécute au first boot uniquement. Pour rejouer, utilisez cloud-init clean + reboot, mais vos scripts doivent être idempotents.
Il y a 5 stages, pas 4. Detect → Local → Network → Config → Final. runcmd tourne au stage Final, après que le réseau et les paquets soient prêts.
Validez toujours avant de déployer. cloud-init schema -c file.yaml --annotate vous évite des heures de debug.
Incus est parfait pour tester. Utilisez les images /cloud et séparez user-data et network-config.
Les logs sont vos amis. /var/log/cloud-init.log et cloud-init status --long répondent à la plupart des questions.
Ressources officielles
Section intitulée « Ressources officielles »- Documentation cloud-init — référence complète
- Boot stages explained — les 5 stages en détail
- Network configuration — pourquoi user-data ≠ réseau
- NoCloud datasource — pour les tests locaux
- Incus cloud-init — intégration Incus
Prochaines étapes
Section intitulée « Prochaines étapes »Maintenant que vous maîtrisez cloud-init, vous pouvez aller plus loin avec des outils de gestion de configuration pour les déploiements complexes.