Cloud-init is the standard way a cloud instance configures itself the first time it boots. You give it a block of user data, and the instance creates users, installs packages, writes files and runs commands on its own, with no manual login. Set it once and every fresh boot of the instance, including after a rebuild, arrives already set up.
What you can put in it
User data takes one of two forms, and it must begin with the right first line.
- A
#cloud-configdocument: YAML that cloud-init understands directly. This is the usual choice. It has to be valid YAML and start with that exact first line. - A script: any file beginning with a
#!shebang, such as#!/bin/bash. Cloud-init runs it on first boot.
Anything else is rejected. The whole block is limited to 16 KB, which is plenty for setup that hands off to your real configuration management.
A working example
This #cloud-config creates a login user with your key, installs a couple of 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
A shell script works too when you would rather script it:
#!/bin/bash
set -euo pipefail
apt-get update && apt-get install -y nginx
systemctl enable --now nginx
Set it in the portal
Open the instance from Cloud and go to its User Data tab. Paste your #cloud-config or script and save. It is validated as you save, so a broken document is caught then rather than at boot. The user data applies on the next fresh boot; to apply it now to a running instance, rebuild from the same image.
With the API
Reading needs read; writing needs the VMs scope (vms.write). Read what is set, then write 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
Send the document as the user_data field. It is validated on write: a document that does not start with #cloud-config or #! answers 422 with code invalid_format; a #cloud-config that is not valid YAML answers 422 invalid_yaml with the parser's message; over 16 KB answers 422 too_large. Because writing validates, you find out about a mistake before a rebuild does.
Questions we get
- When does it run? On the instance's first boot, and on the first boot after a rebuild. Editing it does not re-run it on a running instance; rebuild to apply.
- My config was rejected. Read the
code:invalid_formatmeans the first line is wrong,invalid_yamlmeans the YAML does not parse (the message says where),too_largemeans over 16 KB. - Can I use it to add SSH keys? Yes, with
ssh_authorized_keysin a#cloud-config. You can also pass keys directly on a rebuild. - Does it work on bare metal? No. Bare-metal servers use post-install scripts on reinstall instead; see install profiles.
Still stuck?
Open a support ticket with the instance and your user data, and we can check how cloud-init consumed it on the last boot.