What runs, and when
Every Linux image carries cloud-init. On first boot it reads the user-data attached to your order, applies it once, and writes a marker so it never runs again. All of that happens before you could have typed anything, which is the point: the window between an instance existing and being configured is the window you should not have.
Validate the document before you order. The schema checker catches the indentation mistake that would otherwise cost you a rebuild:
cloud-init schema --config-file user-data.yaml --annotateA user-data that does the useful things
#cloud-config
hostname: edge-01
fqdn: edge-01.example.com
timezone: UTC
users:
- name: deploy
groups: [sudo]
shell: /bin/bash
sudo: "ALL=(ALL) NOPASSWD:ALL"
ssh_authorized_keys:
- ssh-ed25519 AAAAC3Nza... deploy@laptop
ssh_pwauth: false
disable_root: true
package_update: true
package_upgrade: true
packages:
- nftables
- vnstat
- mtr-tiny
write_files:
- path: /etc/ssh/sshd_config.d/10-local.conf
permissions: "0644"
content: |
PermitRootLogin no
PasswordAuthentication no
AllowUsers deploy
runcmd:
- [systemctl, enable, --now, nftables]
- [systemctl, restart, ssh]The first line is neither a comment nor optional. Without #cloud-config the document is ignored in silence and the instance boots as though you had attached nothing.
On AlmaLinux, groups: [sudo] becomes groups: [wheel] and the service to restart is sshd. Everything else is identical.
Watching it work
cloud-init status --long
journalctl -u cloud-final -b
less /var/log/cloud-init-output.logThe journal tells you which stage failed. cloud-init-output.log holds the actual output of your runcmd entries, which is where the reason will be.
Do not put secrets in it
User-data stays readable from inside the instance for its whole life:
cloud-init query userdataAnything in there is available to any process that reaches root, and to anyone who restores a snapshot of the disk. Use it to install a credential that fetches the secret, not to carry the secret.
Testing without burning an instance
cloud-init clean --logs --rebootThat removes the marker, the logs and the cached datasource, then reboots into a fresh first boot. Run it on a throwaway. Run it in production and you will discover what your runcmd does the second time, which is seldom what it did the first.
Where to stop
cloud-init is for bootstrapping and has no idea what state the machine should be in tomorrow. Use it to reach the point where your real tooling can take over: a key, a network, an agent. Stop there. Sixty lines is a healthy user-data, and six hundred is a configuration management system with no way to converge.
cloud-init query -a
cloud-init query ds.meta_dataInstance ID, site code, hostname and the addresses assigned at boot all live in there, which is what a script needs when it has to register itself somewhere and does not yet know where it woke up.