NodeRings Docs

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

  1. Create a profile: Write #cloud-config YAML and save it in your organisation. You can use placeholders for built-in VM details, environment variables, and secrets.
  2. 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.
  3. 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.
  4. 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 fieldCan you change it?
user-dataYes: your saved profile (or deprecated inline paste on the create form)
vendor-dataNo: set by the provider
network-dataNo: 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

SyntaxPurposeExample
{{ expr }}Insert a valuehostname: {{ vm.hostname }}
{{ expr | filter }}Transform a value{{ vm.hostname | default(vm.name) }}
{% if … %}{% endif %}Optional blocksSkip a section when a value is missing
{% for x in list %}LoopsIterate 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

FilterWhy
| default(fallback)Use a fallback when a value is optional (for example hostname).
| tojsonSafely 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

VariableDescription
{{ 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.

VariableDescription
{{ 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.

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

  1. Create a variable (for example key APP_ENV, value staging).
  2. Reference it in your profile.
  3. 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.

  1. Create a secret (for example API_TOKEN). The value is shown once at creation.
  2. Reference it in the profile.
  3. 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.


Next