Configuration files support a variable substitution system that enables a single
template to serve multiple use cases. Variables defined in the YAML variables
section can be overridden from the command line using the --set flag, turning
any config file into a reusable template.
A second mechanism, includes, allows a main config to instantiate a template
multiple times with different variable bindings — similar to GitHub Actions'
uses/with pattern. This replaces per-user (or per-host) config files with a
single template plus a lightweight orchestration file.
Variables are referenced with ${variable_name} syntax and can appear in:
- Mapping fields:
name,source,target - Job fields:
name,source,target - Other variables (variable-to-variable references)
- Variables defined in the YAML
variablessection are loaded - CLI
--setoverrides are merged in (overwriting any matching keys) - Variable self-references are resolved (multi-pass, up to 10 iterations)
- All mapping and job fields are substituted using the fully resolved variables
- Job relative paths are joined with their mapping's base paths to produce absolute paths
This means variables can reference other variables:
variables:
user: alice
mappings:
- name: "home"
source: "/home/${user}"
target: "/mnt/backup1/${user}"
jobs:
- name: "${user}_documents"
source: "Documents"
target: "documents"When invoked with --set user=alice, the resolution chain is:
user=alice(from CLI)- Mapping source =
/home/${user}→/home/alice - Mapping target =
/mnt/backup1/${user}→/mnt/backup1/alice - Job name =
${user}_documents→alice_documents - Job source =
Documentsjoined with/home/alice→/home/alice/Documents/
A config file can declare which variables it requires using a template:
section. When present, the tool validates that every listed variable has a value
before resolving the config — either from the YAML variables: section, a
--set flag, or an include: with: block.
template:
variables:
- user
- user_capIf any declared variable is missing, the tool exits with an error listing the unset variables. This makes template requirements explicit and catches typos or forgotten flags early.
A main config can instantiate one or more templates using the include: section.
Each entry specifies:
uses: path to the template config file (relative to the main config's directory, or absolute)with: map of variable values to inject into the template
include:
- uses: user_template.yaml
with:
user: alice
user_cap: Alice
- uses: user_template.yaml
with:
user: bob
user_cap: Bob- Each
includeentry loads the referenced template file - The
withvalues are merged into the template'svariablesmap - Template variable validation runs (all
template.variablesmust be set) - The template is resolved (variable substitution)
- The resolved mappings (with their jobs) are appended to the main config
- After all includes are expanded, the main config goes through standard validation (job names, paths, overlaps)
- No nested includes: a template referenced via
includecannot itself containincludeentries. This keeps the system simple and predictable. - Include paths are relative to the directory containing the main config file, unless an absolute path is specified.
- A main config can have both its own
sources/targets/jobsandincludeentries — they are merged together.
Template (user_template.yaml):
template:
variables:
- user
- user_cap
sources:
- path: "/home/${user}/"
- path: "/home/data/family/${user_cap}/"
targets:
- path: "/mnt/backup1/${user}"
variables:
source_home: "/home/${user}"
target_base: "/mnt/backup1/${user}"
jobs:
- name: "${user}_mail"
source: "${source_home}/.thunderbird/"
target: "${target_base}/mail"
- name: "${user}_documents"
source: "${source_home}/Documents/"
target: "${target_base}/documents"Main config (users.yaml):
include:
- uses: user_template.yaml
with:
user: alice
user_cap: Alice
- uses: user_template.yaml
with:
user: bob
user_cap: BobRunning backup run --config users.yaml expands both includes and executes all
jobs for both users in a single invocation.
The --set flag can be used with any command (list, run, simulate,
config show, config validate, check-coverage):
# Show resolved config for user "alice"
backup config show --config user_template.yaml --set user=alice --set user_cap=Alice
# Simulate backup for user "bob"
backup simulate --config user_template.yaml --set user=bob --set user_cap=Bob
# Run backup for user "alice"
backup run --config user_template.yaml --set user=alice --set user_cap=Alice
# Run all users via the orchestration config (no --set needed)
backup run --config users.yamlMultiple --set flags can be specified. Later values override earlier ones for
the same key.
| Approach | Best for | Example |
|---|---|---|
--set flags |
Ad-hoc CLI usage, CI scripts, single user | backup run --config tpl.yaml --set user=bob |
include: in config |
Multi-user/multi-host, declarative setups | backup run --config users.yaml |
| Combined | Includes with a shared override | backup run --config users.yaml --set base=/mnt/nfs |
Both approaches can be combined: --set overrides apply to the main config's
variables before includes are expanded. Variables defined inside a template's
with: block are scoped to that template instance only.
- Backward compatible: Configs without
${…}placeholders work unchanged. The--setflag andtemplate:/include:sections are all optional. - Override semantics:
--setvalues take precedence over values defined in the YAMLvariablessection. - Multi-pass resolution: Variable self-references are resolved iteratively (up to 10 passes). Circular references are left unresolved rather than causing an error.
- Validation: Template variable validation runs before resolution to catch missing variables early. Job name validation (uniqueness, character checks) and path validation run on fully resolved configs.
- No nested includes: Keeping the include depth to one level avoids complexity and makes configs easy to reason about.