Fleet organization patterns

When most device configuration is shared across device types, regions, or deployment stages, you can compose layered configuration within a single fleet definition instead of duplicating fleets for each variation.

Layered gitRef composition

A fleet’s template spec contains a spec.config list that can hold multiple gitRef entries, each pointing to a different directory in a Git repository. Entries are applied in list order. When two entries write the same file path, the entry that appears later in the list takes precedence.

This composition model lets you structure configuration as layers:

  • A base layer with configuration shared by all devices in the fleet.
  • One or more override layers that vary by device label, such as region, device type, or deployment stage.

The following fleet template shows a base layer and a region-specific override layer:

spec:
  config:
    - name: base-config
      gitRef:
        repository: site-settings
        targetRevision: main
        path: /config/base
    - name: region-config
      gitRef:
        repository: site-settings
        targetRevision: main
        path: /config/regions/{{ .metadata.labels.region }}

The base-config entry delivers files shared by every device. The region-config entry uses a template placeholder to resolve a region-specific directory per device. A device labeled region: eu-west receives files from /config/regions/eu-west, while a device labeled region: us-east receives files from /config/regions/us-east.

Config entry ordering

Entries in spec.config are applied in the order they appear in the list. If two entries write a file to the same path on the device, the last entry in the list takes precedence. Place override layers after the base layer so that their files replace the base versions.

This ordering rule applies regardless of the provider type. A gitRef entry at position three overrides a conflicting file from an httpRef entry at position two.

Combining multiple provider types

A fleet template is not limited to gitRef entries. You can combine different provider types in a single spec.config list:

gitRef
Version-controlled configuration files stored in a Git repository.
httpRef
Dynamically generated configuration retrieved from HTTP endpoints.
secretRef
Kubernetes secrets containing credentials or certificates.
inline
Small configuration snippets defined directly in the fleet specification.

Template placeholders work in all provider types. For example, you can use {{ .metadata.labels.site }} in a secretRef name to deliver site-specific credentials to each device based on its site label.

Using getOrDefault for optional overrides

When a template placeholder references a device label that a device does not have, the placeholder silently produces an empty string.

Note

A bare placeholder such as {{ .metadata.labels.nonexistent }} resolves to an empty string without raising an error. This can cause devices to receive empty paths or configuration values without any warning. Always use getOrDefault for labels that might not be present on every device.

Use the getOrDefault function to provide a meaningful fallback value instead of an empty string:

path: /config/overrides/{{ getOrDefault .metadata.labels "variant" "default" }}

Devices with a variant label receive the configuration directory that matches their label value. Devices without the variant label receive the default directory instead.

Git repository structure

Organizing your Git repository with a clear directory hierarchy makes the layered composition pattern easier to maintain. The following layout separates base configuration from region-specific and device-type-specific overrides:

site-settings/
  config/
    base/
      motd.conf
      ntp.conf
    regions/
      eu-west/
        ntp.conf
      us-east/
        ntp.conf
    types/
      pos-terminal/
        app.conf
      kiosk/
        app.conf

Each fleet template entry points to a directory at the appropriate level. The base layer points to config/base/, a region override points to config/regions/{{ .metadata.labels.region }}, and a device-type override points to config/types/{{ .metadata.labels.type }}. Files in an override directory replace files from the base layer only when both layers write to the same file path; all other base files remain unchanged.

Template placeholder functions and expressions

Template placeholders support variable substitution and the upper, lower, replace, and getOrDefault functions. You can chain these functions in pipelines, for example {{ getOrDefault .metadata.labels "key" "default" | upper }}.

Template placeholders also support conditional expressions using if, else, else if, and with blocks, along with the comparison functions eq, ne, lt, gt, le, ge, and, or, and not. Loops (range) are not supported in template placeholders.

Note

Inside a with block, dot (.) is rebound to the value of the with expression. Use $ to access root-level fields such as {{ $.metadata.name }} or {{ $.metadata.labels.key }}. Without the $ prefix, field references resolve against the rebound context and fail server-side validation.