Cloud-init is how a cloud instance normally configures itself on its very first boot. You hand it a block of user data, and the instance creates users, installs packages, writes files and runs commands by itself, without anyone logging in. Set it once, and every fresh boot of the instance (a rebuild included) comes up already configured.
What you can put in it
User data comes in one of two forms, and the first line decides which.
- A
#cloud-configdocument, which is YAML cloud-init reads natively. Most people use this one. It must be valid YAML and open with exactly that line. - A script: any file that starts with a
#!shebang, like#!/bin/bash. Cloud-init executes it on first boot.
Anything else gets rejected. The block can be up to 16 KB in total. That's plenty when the setup's job is to hand over to your real configuration management.
A working example
This #cloud-config sets up a login user with your key, installs two packages and starts a service:
#cloud-config
users:
- name: deploy
groups: [sudo]
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... you@example.com
package_update: true
packages:
- nginx
- ufw
runcmd:
- ufw allow OpenSSH
- ufw allow 'Nginx Full'
- ufw --force enable
- systemctl enable --now nginx
If you'd rather write a shell script, that works too:
#!/bin/bash
set -euo pipefail
apt-get update && apt-get install -y nginx
systemctl enable --now nginx
Set it in the portal
Find the instance under Virtual Machines, open it and go to the User Data tab. Paste in your #cloud-config or script and save. Validation happens on save, so you hear about a broken document right away and not at boot. The new user data takes effect on the next fresh boot. To apply it to an instance that's already running, rebuild it from the same image.
With the API
You need read to read it and the VMs scope (vms.write) to write it. Check what's there now, then send your document:
curl -s https://api.amoni.app/v1/vms/990204/411/user-data \
-H "Authorization: Bearer nr_live_..."
# { "success": true, "data": { "user_data": "#cloud-config\n...", "applied_at": "...", "updated_at": "..." } }
curl -s -X PUT https://api.amoni.app/v1/vms/990204/411/user-data \
-H "Authorization: Bearer nr_live_..." -H "Content-Type: application/json" \
--data-binary @cloud-config.yaml # a JSON body { "user_data": "..." } also works
Put the document in the user_data field. Writes are validated. A document that doesn't start with #cloud-config or #! gets 422 with code invalid_format. A #cloud-config that isn't valid YAML gets 422 invalid_yaml along with the parser's message, and anything over 16 KB gets 422 too_large. Since the write checks it, a mistake reaches you before it reaches a rebuild.
Questions we get
- When does it run? At the instance's first boot, and again at the first boot after a rebuild. Changing it won't re-run it on an instance that's up. Rebuild to apply it.
- My config was rejected. Read the
code:invalid_formatmeans the first line isn't right,invalid_yamlmeans the YAML won't parse (the message points to where),too_largemeans you're over 16 KB. - Can I use it to add SSH keys? Yes, with
ssh_authorized_keysin a#cloud-config. Or pass keys straight in when you rebuild. - Does it work on bare metal? No. On bare metal, the equivalent is a post-install script at reinstall time; see install profiles.
Still stuck?
Open a support ticket with the instance and your user data. We can look at how cloud-init handled it on the last boot.