Knowledge base

Do the first-boot work with cloud-init

Attach a user-data document to the order and the instance arrives with your user, your key, your packages and your firewall already in place.

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 --annotate

A 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.log

The 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 userdata

Anything 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 --reboot

That 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_data

Instance 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.

Ready when you are

Pick a city. Pick a size. Pay in coin.

No forms about who you are, no wait for a human to approve you, no phone call to verify anything. The invoice clears and the credentials land in your inbox.