Skip to content

Configuration Reference

A node’s configuration is one TOML file, {home}/{node}/config.toml (--home/CALIMERO_HOME, default ~/.calimero). One struct, node_config::NodeArgs in crates/merod/src/node_config.rs, is both the file and the node flags, so every key has a flag and an environment variable of the same meaning.

For merod run, from strongest to weakest:

  1. a flag on the command line;
  2. its environment variable (MEROD_*, or MERO_TEE_* for the two TEE keys);
  3. the key in {home}/{node}/config.toml, if --node is given and the file exists;
  4. the default, applied when the node starts.

A list key (peers, origins, peer-profiles) given on the command line replaces the file’s list. merod init writes only what it is given; merod config merges what it is given into the file. Both write the file with mode 0600.

  • Keys are kebab-case, as the flags without --.
  • Unknown keys are refused, so a typo fails at start instead of being ignored.
  • Every key is optional in the file; node-id and public-url must be set somewhere before run starts.
  • Defaults are not written, so a file shows only what someone chose.
Key Type Default See
node-id integer required This node’s id in the cluster.
listen "ip:port" "0.0.0.0:2528" Networking
public-url string required Networking
data-dir path {home}/{node}/data Data directory
peers list of "id=url" [] Deployment
bootstrap bool false Deployment
cluster string "dev" Must match on every node.
dev bool false Security
storage-key hex string none dev only.
storage-key-file path none dev only.
kms-url string none Deployment
kms-identity path none Deployment
rp-id string "localhost" Authentication
origins list of URLs ["http://localhost:3000"] Authentication
ops-token string none Authentication
ops-token-sha256 hex string none Authentication
replicas integer 3 How many nodes hold each new app.
backup-dir path none Runbooks
operator-key hex string none Operator
tee-release string none (MERO_TEE_VERSION) Security
tee-profile string none (MERO_TEE_PROFILE) Security
peer-profiles list [tee-profile] Security
peer-min-release string "2.3.109" Security
registry-url string none Registry
allow-unsigned-bundles bool false (true with dev) Raw WASM uploads.
release-approvals "owner" | "operator" "owner" Governance
app-upgrades "owner" | "operator" "owner" Governance
outbound-approvals "owner" | "operator" "owner" Governance
apps-domain string none Networking
settings path none A file whose [outbound] table replaces this one’s.
[outbound] table none (no outbound HTTPS) Outbound
node-id = 1
listen = "0.0.0.0:2528"
public-url = "https://n1.eu1.example.com/"
cluster = "eu1"
peers = [
"1=https://n1.eu1.example.com/",
"2=https://n2.eu1.example.com/",
"3=https://n3.eu1.example.com/",
]
bootstrap = true
kms-url = "https://kms.internal.example.com/"
kms-identity = "/data/disk-identity"
rp-id = "example.com"
origins = ["https://app.example.com"]
ops-token-sha256 = "<hex SHA-256 of the ops token>"
backup-dir = "/backups"
registry-url = "https://registry.example.com/"
[outbound]
enabled = true
hosts = ["api.example.com"]
[outbound.env_secrets.EXAMPLE_API]
env = "EXAMPLE_API_KEY"
packages = ["com.example.forms"]
signers = ["did:key:z6Mk..."]
hosts = ["api.example.com"]

tee-release and tee-profile are usually left to the image, which sets MERO_TEE_VERSION and MERO_TEE_PROFILE.

data-dir holds everything the node persists, sealed under its storage key:

Path What
store/ One RocksDB: the Raft logs, votes, state of every group and the sealed blob store (bundles, .mpk files, app blobs). Every value is AES-256-GCM sealed and bound to its column, group and key.
snapshots/ Raft snapshot files, sealed frame by frame.

A directory that still holds the earlier log.redb file is migrated into store/ on start, and the old file is renamed log.redb.migrated.

registry-url is the package registry every node fetches signed releases from (calimero-app-downloader’s HTTP registry). meroctl app install PACKAGE@VERSION, POST /apps { package, version } for a release the cluster lacks, and owner upgrade runs install from it. A node that runs a registry release it does not hold fetches it from the registry, never from a peer, and refuses bytes whose hash differs from what the log recorded. Without registry-url, those requests answer 400 NO_REGISTRY. Uploaded .mpk files (POST /packages) are taken only with --dev: in production every release comes from the registry.

The [outbound] table (or the [outbound] table of the file settings names, which replaces it) is the operator’s policy for HTTPS requests apps make with env::http_request. Without it, every such request fails.

Key Meaning
enabled true to allow outbound requests at all. Default false.
hosts DNS names requests may go to; "*" allows any host an installed app’s signed manifest declares.
env_secrets.NAME A secret from an environment variable, used in a request as {{secret:NAME}}: env (the variable, read once at start), packages, signers (did:key:…; both must match) and hosts (where it may be sent).
sidecar Secrets from a loopback service instead: url (a loopback http:// URL) and token_env (the variable holding the bearer token sent to it). Exclusive with env_secrets.

A request is made only when all of these hold: the operator allows the host here; the app’s signed .mpk manifest declares a prefix covering the URL; the prefix was approved for the app’s publisher by whoever holds outbound approvals; the run writes (a call, follow-up or timer, never a view or migration); and the URL is https on port 443 to a name that resolves only to public addresses. Secrets are filled in by the node and redacted from the answer the app sees.