cloud-init: Configuring a Machine on Its First Boot
A cloud image is deliberately generic: no hostname, no users, no keys. cloud-init is what turns it into your machine, once, on first boot, before you ever connect.
The model
On first boot, cloud-init looks for a datasource: a metadata service on a cloud platform, or an attached ISO, or a local directory. From it, it reads meta-data describing the instance and user-data describing what you want.
It applies that configuration and records that it is done. Subsequent boots skip most of it.
That once-only behaviour is the thing to internalise, because it explains both why cloud-init is useful and why testing it is annoying.
A basic cloud-config
#cloud-config
hostname: web-01
fqdn: web-01.example.com
users:
- name: deploy
groups: [sudo]
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- ssh-ed25519 AAAAC3Nza... you@laptop
ssh_pwauth: false
disable_root: true
package_update: true
package_upgrade: true
packages:
- ufw
- fail2ban
- htop
write_files:
- path: /etc/ssh/sshd_config.d/99-hardening.conf
content: |
PermitRootLogin no
PasswordAuthentication no
permissions: '0644'
runcmd:
- ufw default deny incoming
- ufw allow 22/tcp
- ufw --force enable
- systemctl restart sshd
final_message: "cloud-init finished after $UPTIME seconds"
The #cloud-config line on the first line is mandatory. Without it the document is treated as something else and silently ignored, which is a common and infuriating first mistake.
This example does most of what our first ten minutes on a new server guide describes, automatically, before you connect.
Using it without a cloud
The NoCloud datasource reads from an attached ISO, which is how libvirt and Proxmox setups use it.
# user-data and meta-data in the current directory
cat > meta-data <<'META'
instance-id: web-01
local-hostname: web-01
META
cloud-localds seed.iso user-data meta-data
Attach seed.iso as a CD-ROM alongside the disk image and boot. Proxmox has this built into its VM configuration as a cloud-init drive.
Debugging
cloud-init status --long
sudo cat /var/log/cloud-init-output.log # what ran, and its output
sudo cat /var/log/cloud-init.log # detail
cloud-init schema --system # validate the config it received
cloud-init-output.log is the one to read first. It contains the actual output of your runcmd entries, which is where most failures are visible.
To test repeatedly on the same VM:
sudo cloud-init clean --logs
sudo reboot
That resets the completion state so everything runs again. Doing this on a snapshot is the sane workflow.
Where it stops
cloud-init runs once. It is not configuration management, and using runcmd to do substantial setup produces something unrepeatable and undebuggable.
The division that works: cloud-init makes the machine reachable with your key, a hostname, and a firewall. Everything ongoing belongs to a configuration tool that can run repeatedly and converge.
Frequently Asked Questions
What does cloud-init actually do?
It runs early on first boot, reads configuration from a metadata source, and applies it: setting the hostname, creating users, installing SSH keys, configuring networking, writing files, and running commands. It then marks itself done so it does not repeat.
Why does my cloud-init change not apply on reboot?
Because most of it is designed to run once. cloud-init records that it has completed and skips those stages afterwards. To test changes repeatedly you have to clean that state with cloud-init clean, which is what makes iterating awkward.
What is the difference between user-data and meta-data?
meta-data is provided by the platform and describes the instance, including its ID and hostname. user-data is what you supply, usually a cloud-config YAML document describing what you want configured.
Can I use cloud-init outside a cloud provider?
Yes. The NoCloud datasource reads user-data and meta-data from a small ISO attached to the VM or from a local directory, which is how Proxmox and libvirt setups use it on machines that are not in any cloud.
How do I debug cloud-init when it does not work?
Read /var/log/cloud-init-output.log first, which shows what ran and what it printed, then /var/log/cloud-init.log for detail. Also validate your YAML, since a malformed document is silently skipped in some versions.
Should I use cloud-init or a configuration management tool?
Both, for different jobs. cloud-init handles first boot: get the machine reachable with your key and a hostname. Ansible or similar handles ongoing configuration, which cloud-init is not designed for because it runs once.