Skip to main content

Render inventory with templates

Templates use Jinja expressions to read data from the inventory and render them.

Use Templates → Data reference in the UI to see the supported datatypes, fields, filters, and relationships available in the version you are running. There are also examples there, as this changes between versions .

A small template workflow

  1. Create a configuration template and choose an output filename such as nginx.conf, traefik.yml, or inventory-report.md.
  2. Start with Render now to preview the template against current accepted inventory.
  3. Generate an output snapshot. A snapshot records the template revision and rendered content.
  4. Inspect History & outputs before approving or publishing anything.

Saving source creates a new revision. Keep generated files tied to the revision that produced them; this makes a changed route or missing device explainable later.

MiniPAM template editor with Jinja source and rendered output

The template editor displays source and current rendered output.

Nginx reverse-proxy configuration

This example emits one HTTP server block for each gateway route. It follows each route to its service, chooses the first application for that service, then finds an address for that application. Routes without a usable application or address are skipped rather than rendered as invalid upstreams.

{% for route in data.gateway_routes.sort('fqdn') %}
{% set service = route.service %}
{% set application = data.applications.of(service).first() %}
{% set address = data.ip_addresses.of(application).first() if application else none %}
{% if application and address %}
upstream {{ route.fqdn|replace('.', '_') }} {
server {{ address.address }}:{{ application.port }};
}

server {
listen 80;
server_name {{ route.fqdn }};

location {{ route.path_prefix or '/' }} {
proxy_pass http://{{ route.fqdn|replace('.', '_') }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
{% endif %}
{% endfor %}

Point your deployment at the generated file, then validate it with the target tool (nginx -t) before reloading Nginx. A template render is not a deployment and does not validate Nginx syntax for you.

Traefik dynamic configuration

Traefik's file provider accepts YAML. This example emits one route for Grafana; change the FQDN filter and service name for another route. The optional path prefix becomes a PathPrefix matcher.

{% set route = data.gateway_routes.filter('fqdn:eq:grafana.home.example').first() %}
{% set application = data.applications.of(route.service).first() if route else none %}
{% set address = data.ip_addresses.of(application).first() if application else none %}
{% if route and application and address %}
http:
routers:
grafana:
rule: "Host(`{{ route.fqdn }}`){% if route.path_prefix %} && PathPrefix(`{{ route.path_prefix }}`){% endif %}"
service: grafana
services:
grafana:
loadBalancer:
servers:
- url: "http://{{ address.address }}:{{ application.port }}"
{% endif %}

Save this as a YAML output, run it through your normal YAML and Traefik checks, and only then copy or publish it to Traefik's watched directory. If a service has several applications, make the selection rule explicit, using a tag or sorted/limited query, instead of relying on the first record.

A human-readable inventory report

Templates can also produce human-readable reports. This example renders a Markdown inventory report.

# {{ data.site.name }} inventory report

Generated: {{ data.generated_at }}
Networks: {{ data.networks.count() }}
Devices: {{ data.devices.count() }}
Services: {{ data.services.count() }}

## Devices

{% for device in data.devices.sort('name') %}
### {{ device.name }}
{% if device.hostname %}- Hostname: {{ device.hostname }}
{% endif %}- Type: {{ device.device_type.name if device.device_type else 'Unclassified' }}
{% if device.description %}- Notes: {{ device.description }}
{% endif %}{% for address in data.ip_addresses.of(device) %}- Address: {{ address.address }}
{% endfor %}
{% endfor %}

## Services

{% for service in data.services.sort('name') %}
- **{{ service.name }}**{% if service.category %} ({{ service.category }}){% endif %}
{% endfor %}

Keep reports bounded for large sites: filter to a site-scoped collection, call limit() when appropriate, and reuse a query stored with {% set ... %}. MiniPAM limits data operations, materialized records, and output size so a template cannot run indefinitely.

Snapshots, checks, and automation

Use the separate asset-integrity checks to assert that important inventory or output assumptions still hold. Attach blocking checks to a configuration template before enabling automatic publication.

Automation can run on a schedule or after the fixed inventory-change coalescing window. Start with manual previews and snapshots, validate the generated file with its target application, then enable one trigger. Rendering stays read-only; publication is the explicit final step.

MiniPAM template automation settings
Template automation can run after an inventory change or on a schedule.