cloud-init: Configuring a Machine on Its First Boot

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.