Skip to content

Cloud-Init and the Role System

What is Cloud-Init?

Cloud-init is the industry standard tool for configuring Linux machines on first boot. It handles common setup tasks such as creating users, installing packages, writing files, running commands, and configuring network interfaces.

Durantic generates cloud-init configurations from your machine roles and delivers them during provisioning. When the machine boots into its new operating system, cloud-init reads this configuration and applies it automatically.

The Role System

In Durantic, a role is a reusable configuration template that produces cloud-init YAML. Each role contains a Jinja2 template that is rendered at provisioning time with machine-specific context.

Roles are designed to be reusable — you write a role once and assign it to as many machines as needed. A machine can have multiple roles, and Durantic merges their outputs into a single cloud-init configuration.

Roles list

Creating Effective Roles

A role template is a Jinja2 file that outputs valid cloud-init YAML. Here is a basic example:

#cloud-config
users:
  - name: admin
    sudo: ALL=(ALL) NOPASSWD:ALL
    ssh_authorized_keys:
      - {{ vars.admin_ssh_key }}

packages:
  - curl
  - htop

runcmd:
  - systemctl enable --now docker

The {{ vars.admin_ssh_key }} expression is replaced at render time with the value of the admin_ssh_key variable defined in your account.

Editing a role

Merge Priority

When a machine has multiple roles, Durantic merges their cloud-init outputs based on each role's merge priority — a numeric value you assign to each role.

  • Lower numbers are applied first.
  • Higher numbers are applied on top, overriding conflicts.

For example:

Priority Role Purpose
0 base-linux Common packages, base users, SSH hardening
100 webserver Nginx installation, firewall rules
200 tls-termination Certificate files, HTTPS configuration

This layered approach lets you compose machine configurations from small, focused roles rather than writing a single monolithic template.

Merge Rules

When two roles produce overlapping cloud-init sections, Durantic follows these merge rules:

Data Type Merge Behavior
Dicts (maps) Deep recursive merge — keys from both roles are combined
Lists (arrays) Concatenated — items from all roles are joined into one list
Scalars (strings, numbers, booleans) Higher priority wins — the role with the higher priority number takes precedence

Template Context

Role templates have access to a rich context when they are rendered. The following variables and objects are available inside your Jinja2 templates.

Machine Properties

Variable Description
machine.hostname The machine's hostname
machine.roles List of roles assigned to the machine
machine.hardware.cpu CPU information
machine.hardware.memory Memory capacity
machine.hardware.storage Storage devices
machine.hardware.gpu GPU information (if present)
machine.interfaces Network interfaces and their addresses
machine.mesh.ip The machine's mesh network IP (or None if not in a mesh)
machine.mesh.cidr The mesh network CIDR
machine.mesh.netmask The mesh network netmask

Peers

The peers variable contains a list of all machines in the same mesh network as the current machine. This enables peer discovery patterns in your templates.

Variables and Secrets

Syntax Description
vars.NAME Plain-text variable defined in your account
secrets.NAME Encrypted secret, decrypted at render time by the control plane
${secrets:backend:path:key} Agent-resolved secret — resolved on the machine itself, never passes through the control plane

Variables

Secrets

Peer Discovery Pattern

Use the peers list in your templates to dynamically discover related machines. Filter by role to find machines that serve a specific function:

#cloud-config
write_files:
  - path: /etc/app/cluster-nodes.conf
    content: |
      {% for peer in peers if 'database' in peer.roles %}
      node={{ peer.mesh.ip }}
      {% endfor %}

This pattern is useful for building cluster configurations, service discovery files, or load balancer backends without hardcoding IP addresses.

Roles Without Templates

A role does not need a template. Roles with no template act as tags — they exist for organizational purposes such as:

  • Peer discovery — Other roles can filter peers by this role name.
  • Dashboard grouping — Filter and search machines by role in the machines list.
  • Policy targeting — Apply route policies or mesh rules based on role membership.

Previewing Configuration

Before provisioning, you can preview the final merged cloud-init configuration that will be applied to a machine.

  1. Navigate to the machine's detail page.
  2. Open the Preview tab.

The preview shows the fully rendered and merged cloud-init YAML, with all Jinja2 expressions evaluated and all roles merged in priority order.

Cloud-init preview

Testing Roles

You can test a role template against a specific machine without triggering provisioning.

  1. Navigate to the role's detail page.
  2. Open the Test tab.
  3. Select a target machine.
  4. View the rendered output.

This lets you verify that your Jinja2 expressions resolve correctly and that the output is valid cloud-init YAML before applying it to real machines.

Testing a role

Using Variables and Secrets

Durantic provides three mechanisms for injecting dynamic values into your role templates:

Plain-Text Variables (vars)

Defined in the Variables section of the dashboard. Use these for non-sensitive configuration such as SSH public keys, domain names, or feature flags.

hostname: {{ vars.app_domain }}

Encrypted Secrets (secrets)

Defined in the Secrets section of the dashboard. Values are stored encrypted and decrypted at render time by the control plane. Use these for passwords, API keys, or certificates that the control plane needs to embed into cloud-init.

password: {{ secrets.db_password }}

Agent-Resolved Secrets (${secrets:...})

These references are not resolved by the control plane. Instead, the literal string is placed into the cloud-init configuration, and the agent resolves it on the machine itself. The secret value never passes through the control plane.

api_key: ${secrets:vault:kv/data/app:api_key}

Use agent-resolved secrets when you need to reference secrets from an external secrets manager and want to ensure the control plane never has access to the plaintext value.