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 commandes couvrent l'essentiel du travail avec cloud-init : valider une configuration, suivre son exécution et déboguer un déploiement qui se comporte mal.
Valider un fichier user-data
Section intitulée « Valider un fichier user-data »Vérifier la syntaxe et les directives avant de déployer la VM — bien moins coûteux qu'un boot raté.
cloud-init schema -c FICHIER --annotate# Exemplecloud-init schema -c user-data.yaml --annotateFICHIER est le chemin vers le fichier user-data à contrôler.
Vérifier l'état d'exécution
Section intitulée « Vérifier l'état d'exécution »Savoir si cloud-init a terminé et s'il a rencontré des erreurs.
cloud-init status --longConsulter les logs
Section intitulée « Consulter les logs »Voir ce que cloud-init a réellement fait et repérer les erreurs.
sudo less /var/log/cloud-init.log# Cibler directement les erreurssudo grep -i error /var/log/cloud-init.logAnalyser les performances
Section intitulée « Analyser les performances »Identifier les modules lents qui rallongent le premier boot.
cloud-init analyze blame | head -10Forcer une ré-exécution
Section intitulée « Forcer une ré-exécution »Supprimer l'état de cloud-init pour le rejouer au prochain boot — pratique en phase de mise au point.
sudo cloud-init clean --logs && sudo rebootTester avec Incus
Section intitulée « Tester avec Incus »Lancer un conteneur avec un user-data pour un test local rapide, sans provisionner de VM cloud.
incus launch images:ubuntu/noble/cloud NOM \ --config=cloud-init.user-data="$(cat FICHIER)"# Exempleincus launch images:ubuntu/noble/cloud test \ --config=cloud-init.user-data="$(cat user-data.yaml)"NOM est le nom du conteneur, FICHIER le fichier user-data.
Attendre la fin de cloud-init
Section intitulée « Attendre la fin de cloud-init »Bloquer jusqu'à ce que cloud-init termine, utile dans un script de test.
incus exec CONTENEUR -- cloud-init status --wait# Exempleincus exec test -- cloud-init status --waitRejouer un module spécifique
Section intitulée « Rejouer un module spécifique »Exécuter un seul module sans rebooter toute la machine.
sudo cloud-init single --name MODULE# Exemplesudo cloud-init single --name cc_runcmdMODULE est le nom du module ciblé (cc_runcmd, cc_write_files, etc.).
Pièges courants
Section intitulée « Pièges courants »Ces erreurs reviennent constamment et produisent des VMs « à moitié configurées ». Les connaître évite des heures de diagnostic au premier boot.
Mettre la configuration réseau dans user-data
Section intitulée « Mettre la configuration réseau dans user-data »runcmd: - ip addr add 10.0.0.50/24 dev eth0Symptôme : le réseau ne se configure pas, ou se réinitialise au boot suivant.
Cause : user-data ne peut pas configurer le réseau — c'est une source de données séparée. Passez par cloud-init.network-config (Incus) ou par le metadata service du provider.
Oublier #cloud-config
Section intitulée « Oublier #cloud-config »users: - name: deploySymptôme : le fichier est ignoré, aucune configuration n'est appliquée.
Cause : sans #cloud-config en première ligne, cloud-init ne reconnaît pas le format.
#cloud-configusers: - name: deployAttendre runcmd trop tôt
Section intitulée « Attendre runcmd trop tôt »Vous cherchez un fichier créé par runcmd juste après le boot.
Symptôme : le fichier n'existe pas encore.
Cause : runcmd s'exécute au stage Final, après packages et write_files. Attendez avec cloud-init status --wait, ou consultez /var/log/cloud-init-output.log.
Scripts non idempotents avec cloud-init clean
Section intitulée « Scripts non idempotents avec cloud-init clean »runcmd: - useradd deploySymptôme : erreur user already exists après un cloud-init clean suivi d'un reboot.
Cause : useradd échoue si l'utilisateur existe déjà. Préférez la directive users:, qui est idempotente.
users: - name: deploy groups: sudoSecrets dans user-data
Section intitulée « Secrets dans user-data »write_files: - path: /etc/app/secrets.env content: API_KEY=sk-xxxSymptôme : les secrets sont exposés via le metadata service, lisible par toute la VM.
Cause : user-data est accessible via http://169.254.169.254/latest/user-data. Utilisez Vault, un gestionnaire de secrets, ou récupérez les secrets au runtime.
Indentation YAML incorrecte
Section intitulée « Indentation YAML incorrecte »packages:- nginx - gitSymptôme : erreur de parsing ou comportement inattendu.
Cause : YAML est strict sur l'indentation — des espaces, jamais de tabulations. Validez systématiquement avec cloud-init schema -c fichier.yaml --annotate.
Confondre package et packages
Section intitulée « Confondre package et packages »package: - nginxSymptôme : la directive est ignorée, les paquets ne sont pas installés.
Cause : la directive correcte est packages, avec un « s ».
packages: - nginxDé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.