Cloud-Init User Data
Saved user_data profiles that replace provider bootstrap at VM create
Cloud-init user data profiles let you save reusable #cloud-config YAML and attach them when you launch a VM. Your profile replaces the provider's user-data for that launch. The provider still applies vendor-data and network-data (static IP, DNS, routes): you cannot override those.
Manage profiles at /organization/cloudinit-template under Orchestration → Cloud-Init Templates.
How it works
- Create a profile: Write
#cloud-configYAML and save it in your organisation. You can use placeholders for built-in VM details, environment variables, and secrets. - Launch a VM: On create, choose Cloud-init profile and select one profile. Password and SSH key options on the create form are disabled; configure login inside the profile instead.
- Values are applied at launch: Built-in variables, env vars, and secrets are resolved into your user-data and sent to the VM. The guest runs cloud-init once on first boot.
- Existing VMs are unchanged: Editing a profile or updating an env var or secret does not re-run cloud-init on VMs that are already running. New values apply only to VMs created after the change.
Expand a profile row in the list to preview the saved YAML with syntax highlighting. Use Edit from the row menu or profile name to change it.
What you can and cannot change
| Cloud-init field | Can you change it? |
|---|---|
| user-data | Yes: your saved profile (or deprecated inline paste on the create form) |
| vendor-data | No: set by the provider |
| network-data | No: set by the provider (static IP, DNS, routes) |
Built-in placeholders such as {{ vm_network.ipv4_address }} let you read network facts inside your user-data (for config files or scripts). They do not replace the provider's network configuration.
Placeholders in profiles
Profiles support Jinja2 placeholders: expressions in {{ … }} that are replaced with real values when the VM is created.
Basic syntax
| Syntax | Purpose | Example |
|---|---|---|
{{ expr }} | Insert a value | hostname: {{ vm.hostname }} |
{{ expr | filter }} | Transform a value | {{ vm.hostname | default(vm.name) }} |
{% if … %} … {% endif %} | Optional blocks | Skip a section when a value is missing |
{% for x in list %} | Loops | Iterate DNS servers |
Dot notation: {{ vm.name }}, {{ env.APP_ENV }}, {{ secret.API_TOKEN }}
Bracket notation (for keys with special characters): {{ env['MY_KEY'] }}, {{ secret["API_TOKEN"] }}
Filters you should use
| Filter | Why |
|---|---|
| default(fallback) | Use a fallback when a value is optional (for example hostname). |
| tojson | Safely embed strings inside YAML (quotes, newlines, special characters). Use this for env, secret, and dynamic values in write_files or scripts. |
Example:
#cloud-config
hostname: {{ vm.hostname | default(vm.name) }}
write_files:
- path: /etc/myapp/config.env
content: |
HOST={{ vm.hostname | tojson }}
APP_ENV={{ env.APP_ENV | tojson }}
API_TOKEN={{ secret.API_TOKEN | tojson }}Cloud-init profile auth does not use the create-form password or SSH key pickers. Configure users and keys inside the profile (for example hashed_passwd, ssh_authorized_keys, or {{ secret.hashed_passwd }}). Use cloud-init hashed_passwd with a SHA-512 crypt hash ($6$…) — never plaintext passwords in the profile.
Built-in variables
These placeholders are filled in from your VM order and assigned networking when the VM is created. Use only the documented names: typos (for example vm.host_name) are flagged when you save the profile.
vm: identity
| Variable | Description |
|---|---|
{{ vm.name }} | VM identifier. Always available. |
{{ vm.hostname }} | Hostname from the create form, when you set one. |
vm.hashed_passwd and vm.ssh_keys are not available when you use profile auth: set login in the profile itself.
vm_network: networking
Use these in config files or scripts. The provider still configures interfaces via network-data.
| Variable | Description |
|---|---|
{{ vm_network.mac_address }} | Primary network MAC address. |
{{ vm_network.ipv4_address }} | Assigned IPv4 address. |
{{ vm_network.ipv4_prefix }} | IPv4 prefix length (for example 24). |
{{ vm_network.ipv4_gateway }} | IPv4 default gateway. |
{{ vm_network.ipv6_address }} | Assigned IPv6 address. |
{{ vm_network.ipv6_prefix }} | IPv6 prefix length. |
{{ vm_network.ipv6_gateway }} | IPv6 default gateway. |
{{ vm_network.gateway }} | IPv4 gateway (when present). |
{{ vm_network.dns_servers }} | DNS servers (list: use {% for dns in vm_network.dns_servers %}). |
Organisation variables (env and secret)
Reference values from your organisation: not the provider's.
| Placeholder | Source | Manage at |
|---|---|---|
{{ env.KEY }} | Non-secret config | /organization/environment-variables |
{{ secret.KEY }} | Secret value | /organization/secrets |
Environment variables
Use env.* for non-sensitive configuration: environment names, feature flags, public URLs, and similar.
- Create a variable (for example key
APP_ENV, valuestaging). - Reference it in your profile.
- When you launch a VM, the current value is written into the final user-data.
#cloud-config
write_files:
- path: /etc/myapp/env
owner: root:root
permissions: "0644"
content: |
APP_ENV={{ env.APP_ENV | tojson }}
LOG_LEVEL={{ env.LOG_LEVEL | tojson }}See Environment variables for reserved key names.
Secrets
Use secret.* for passwords, API tokens, and other sensitive values. Do not paste secret values into profile YAML: create a secret and reference it by key.
- Create a secret (for example
API_TOKEN). The value is shown once at creation. - Reference it in the profile.
- At VM launch, the value is included in the user-data sent to the guest.
#cloud-config
write_files:
- path: /root/.config/api_token
owner: root:root
permissions: "0600"
content: {{ secret.API_TOKEN | tojson }}See Secrets for rotation: running VMs keep the value from create time until you replace them.
Validation warnings
When you save a profile, the UI checks YAML structure and whether each env.* and secret.* reference matches a key in your org. Missing keys show warnings on the profile. You can still save, but fix warnings before production use.
Reserved key names: do not create env vars or secrets whose keys collide with built-in names (vm, vm_network, password, env, secret, and similar). Create and update will be rejected.
Example profile: login, SSH, and variable dump
This example:
- Sets hostname from the VM order
- Enables SSH password authentication and root login
- Sets a root password in the profile (required because profile auth disables the create-form password field)
- Writes a file listing built-ins, env vars, and secrets for testing
Create matching org entries first: for example env key envtestkey and secret key secrettestkey: or change the references to keys you already use.
#cloud-config
hostname: {{ vm.hostname | default(vm.name) }}
ssh_deletekeys: false
ssh_genkeytypes: [rsa, ecdsa, ed25519]
disable_root: false
ssh_pwauth: true
users:
- name: root
lock_passwd: false
# SHA-512 crypt hash only, e.g. from: mkpasswd -m sha-512
hashed_passwd: "$6$rounds=656000$exampleSalt$replaceWithYourOwnHash................................"
chpasswd:
expire: false
runcmd:
- mkdir -p /etc/ssh/sshd_config.d
- awk 'BEGIN { print "PermitRootLogin yes"; print "PasswordAuthentication yes" }' > /etc/ssh/sshd_config.d/99-nrings-root-password.conf
- sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config
- systemctl try-reload-or-restart sshd 2>/dev/null || systemctl try-reload-or-restart ssh 2>/dev/null || true
write_files:
- path: /etc/nrings-test/all-variables.txt
owner: root:root
permissions: "0600"
content: |
# Built-ins
vm.name={{ vm.name | tojson }}
vm.hostname={{ vm.hostname | tojson }}
vm_network.mac_address={{ vm_network.mac_address | tojson }}
vm_network.ipv4_address={{ vm_network.ipv4_address | tojson }}
vm_network.ipv4_prefix={{ vm_network.ipv4_prefix | tojson }}
vm_network.ipv4_gateway={{ vm_network.ipv4_gateway | tojson }}
vm_network.dns_servers={{ vm_network.dns_servers | tojson }}
# Org variables
env.envtestkey={{ env.envtestkey | tojson }}
secret.secrettestkey={{ secret.secrettestkey | tojson }}Create a profile
Write valid cloud-config
Start with #cloud-config on the first line. Use YAML structure cloud-init expects: users, packages, write_files, runcmd, and so on.
Add placeholders where helpful
Use built-ins for hostname and network facts, and env.* / secret.* for org configuration. Prefer | tojson inside file contents and scripts.
Save and review warnings
Fix validation warnings before relying on the profile in production. Expand the row in the list to review the full YAML.
Invalid user-data can prevent boot. Test profiles on non-production VMs first. You are responsible for valid cloud-config when using profile auth.
Use at VM create
On /platform/virtual-machines/create, choose Cloud-init profile as the authentication method and select a saved profile.
This method is mutually exclusive with password auth and SSH key selection: your profile must configure users, passwords, and/or keys.
Inline user-data paste on the create form is deprecated in favour of saved profiles. Prefer profiles for reuse, validation, and org variable support.