Cloud-Init Templates
User, vendor, and network data synced to the agent for VM provisioning
Cloud-init templates define the user-data, vendor-data, and network-data your operator injects when cloning a VM. Templates support Jinja2 syntax. When a VM is created, NodeRings compiles each field and substitutes values from the order, IPAM static assignment, and the customer's SSH keys or password.
NodeRings assigns static IPs only through IPAM. Configure fixed addresses, gateways, and DNS in your templates. Not DHCP.
When you save a template, NodeRings syncs the compiled files to your linked agents so the operator can use them at provision time.
Manage templates at /infrastructure/cloud-init.
Seed templates
NodeRings ships read-only seed templates for common guest OS families (for example Ubuntu/Debian, Fedora, Rocky Linux). They are visible to every organization but cannot be edited, deleted, or linked directly to a VM template.
To use one:
Cloud-Init Templates page
Step 1 of 2
- Open Cloud-Init Templates → New and pick the seed that matches your guest OS.
- NodeRings loads a draft with
user_data,vendor_data, andnetwork_data. - Adjust storage or network matching for your environment, then save each file to create your own template.
Your copy is a normal organization template you link when registering VM templates.
Template fields
| Field | Typical content |
|---|---|
| user-data | Users, SSH keys, packages, hostname |
| vendor-data | Vendor-specific metadata |
| network-data | Interfaces, static IPs, routes, DNS |
Provided variables
NodeRings fills these Jinja variables when your operator compiles a template at VM create. Use {% if … is defined %} guards only where a field may be absent from the order. Every provisioned VM receives both IPv4 and IPv6 from IPAM.
vm: identity and access
| Variable | Type | Description |
|---|---|---|
{{ vm.name }} | string | Proxmox VM name on the hypervisor (for example NR-{uuid}). Always set. |
{{ vm.hostname }} | string | Hostname from the VM order. Set when the customer supplies a hostname. |
{{ vm.ssh_keys }} | list | SSH public keys from the order. Set when the customer supplies SSH keys. |
{{ vm.hashed_passwd }} | string | SHA-512 crypt hash ($6$…) of the root password from the order. Set when the customer chooses password authentication. Use with cloud-init hashed_passwd — never plaintext. |
Password auth: NodeRings hashes the create-form password in the backend before cloud-init compile. Templates must use vm.hashed_passwd with cloud-init hashed_passwd only — do not use plaintext password fields.
vm_network: IPAM static assignment
Populated from NodeRings IPAM when the VM is provisioned. Every customer VM receives a managed static address. Use these variables in network-data.
| Variable | Type | Description |
|---|---|---|
{{ vm_network.ipv4_address }} | string | Assigned IPv4 address. |
{{ vm_network.ipv4_prefix }} | string | IPv4 prefix length (for example 24). |
{{ vm_network.ipv6_address }} | string | Assigned IPv6 address. |
{{ vm_network.ipv6_prefix }} | string | IPv6 prefix length. |
{{ vm_network.gateway }} | string | Default gateway. |
{{ vm_network.dns_servers }} | list | DNS resolvers. Iterate with {% for dns in vm_network.dns_servers %}. |
{{ vm_network.mac_address }} | string | MAC address NodeRings allocated for the VM's primary NIC (lowercase). Use it to match: macaddress: in network-data so the interface is found regardless of its name inside the guest. |
Organisation variables
Provider templates can reference organisation-scoped configuration stored under Configuration → Environment Variables and Configuration → Secrets. NodeRings compiles these references at VM create time in the backend and writes the final YAML to a per-VM secret: the operator receives rendered cloud-config, not raw Jinja with env.* / secret.*.
| Variable | Source | Example |
|---|---|---|
{{ env.<key> }} | Provider org environment variable | {{ env.app_env }} |
{{ secret.<key> }} | Provider org secret (Vault) | {{ secret.api_token }} |
Bracket syntax is also supported: {{ env['MY_KEY'] }}, {{ secret["API_TOKEN"] }}.
Reserved key names: do not create env vars or secrets whose keys collide with built-in namespaces (vm, vm_network, hashed_passwd, password, env, secret, and so on). See Environment variables and Secrets.
Create-time only: changing an env var or rotating a secret does not re-provision running VMs. New values apply to VMs created after the change. See the operations runbook.
Example user-data snippet:
#cloud-config
write_files:
- path: /etc/nrings/app.env
content: |
APP_ENV={{ env.app_env | tojson }}
API_TOKEN={{ secret.api_token | tojson }}When a template's user-data contains env.* or secret.* references, NodeRings pre-renders it per VM. Templates with built-ins only (vm.*, vm_network.*) continue to compile in the operator unchanged.
Manage org configuration at /configuration/env-vars and /configuration/secrets.
Example template
user-data and network-data for static IP provisioning. Seed templates follow the same structure; network-data may differ by guest OS (netplan, NetworkManager, and so on).
#cloud-config
hostname: {{ vm.hostname | default(vm.name) }}
ssh_deletekeys: false
ssh_genkeytypes: [rsa, ecdsa, ed25519]
disable_root: false
{% if vm.hashed_passwd is defined and vm.hashed_passwd %}
ssh_pwauth: true
write_files:
- path: /etc/ssh/sshd_config.d/99-nrings-root-password.conf
permissions: "0644"
content: |
PermitRootLogin yes
PasswordAuthentication yes
runcmd:
- rm -f /etc/ssh/sshd_config.d/50-cloud-init.conf /etc/ssh/sshd_config.d/60-cloudimg-settings.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
{% endif %}
{% if vm.hashed_passwd is defined and vm.hashed_passwd or (vm.ssh_keys is defined and vm.ssh_keys) %}
users:
- name: root
{% if vm.hashed_passwd is defined and vm.hashed_passwd %}
lock_passwd: false
hashed_passwd: {{ vm.hashed_passwd | tojson }}
{% else %}
lock_passwd: true
{% endif %}
{% if vm.ssh_keys is defined and vm.ssh_keys %}
ssh_authorized_keys:
{% for key in vm.ssh_keys %}
- {{ key }}
{% endfor %}
{% endif %}
{% endif %}#network-config
version: 2
ethernets:
primary:
{% if vm_network.mac_address is defined and vm_network.mac_address %}
match:
macaddress: {{ vm_network.mac_address }}
{% else %}
match:
name: "en*"
{% endif %}
{% if vm_network.ipv4_address is defined and vm_network.ipv4_address or (vm_network.ipv6_address is defined and vm_network.ipv6_address) %}
dhcp4: false
dhcp6: false
addresses:
{% if vm_network.ipv4_address is defined and vm_network.ipv4_address and vm_network.ipv4_prefix is defined and vm_network.ipv4_prefix %}
- {{ vm_network.ipv4_address }}/{{ vm_network.ipv4_prefix }}
{% endif %}
{% if vm_network.ipv6_address is defined and vm_network.ipv6_address and vm_network.ipv6_prefix is defined and vm_network.ipv6_prefix %}
- {{ vm_network.ipv6_address }}/{{ vm_network.ipv6_prefix }}
{% endif %}
{% if vm_network.dns_servers is defined and vm_network.dns_servers %}
nameservers:
addresses:
{% for dns in vm_network.dns_servers %}
- {{ dns }}
{% endfor %}
{% endif %}
{% if (vm_network.ipv4_gateway is defined and vm_network.ipv4_gateway) or (vm_network.gateway is defined and vm_network.gateway) or (vm_network.ipv6_gateway is defined and vm_network.ipv6_gateway) %}
routes:
{% if (vm_network.ipv4_gateway is defined and vm_network.ipv4_gateway) or (vm_network.gateway is defined and vm_network.gateway) %}
- to: default
via: {{ vm_network.ipv4_gateway | default(vm_network.gateway) }}
{% endif %}
{% if vm_network.ipv6_gateway is defined and vm_network.ipv6_gateway %}
- to: default
via: {{ vm_network.ipv6_gateway }}
{% endif %}
{% endif %}
{% else %}
dhcp4: true
dhcp6: true
{% endif %}