Tillered Docs

Compose file reference

Full schema reference for the cluster.yaml compose file

The compose file declares the desired state of an Arctic cluster in YAML. Apply it with arctic compose apply. For a task-oriented walkthrough, see the compose guide; for the CLI's own configuration file, see the CLI configuration reference.

File format

Compose files use schema version v1. The file can be named anything, but cluster.yaml is the convention. Two top-level optional blocks, requires and server, were added in v1.4.0; both are backwards-compatible.

Root structure

FieldTypeRequiredDescription
versionstringYesSchema version; must be v1
licensestringNoPath to the license file, relative to the config file location
requiresobjectNoVersion constraints every peer must satisfy before apply or diff proceeds
serverobjectNoDesignated server peer for centralized operations. Absent means fully decentralized
peersarrayYesPeer definitions
servicesarrayNoService definitions

requires

Version constraints checked after pre-flight connectivity and before diff or apply. Skip with --skip-requires.

FieldTypeDescription
agentstringConstraint on every peer's running agent version

Constraint syntax: vX.Y.Z (exact), ~vX.Y.Z (patch range), or ^vX.Y.Z (minor range).

requires:
  agent: "^v1.4.0"

server

Optional. By default a cluster is managed as a mesh: every peer accepts operator API calls, and the operator needs network access to all peers. Declaring a server block shifts management to a hub-and-spoke shape: the named peer becomes the cluster's point of entry, taking centralized operations such as the cluster apply lock and managing the other peers over the cluster on the operator's behalf.

Only management changes shape. The underlying network is a mesh either way - peers still gossip and carry traffic directly - and the server peer holds no special state authority; "server" is an entry-point role. The default mesh is the simplest and most consistent choice when the operator can reach every peer. Declare a server when the other peers cannot or should not be reached directly, typically together with api_access: internal on those peers so a single front-door node manages an otherwise unreachable cluster. See Clustering for the model.

FieldTypeRequiredDescription
peerstringYesName of the primary server peer; must reference a peer in the peers list
fallback_peerstringNoSingle fallback used only when the primary is unreachable at the connection level
server:
  peer: hub
  fallback_peer: hub-standby

Peer configuration

FieldTypeRequiredDescription
namestringYesUnique identifier for this peer; referenced by services
descriptionstringNoFree-form description shown in operator output; not gossiped
typestringNoPeer role: agent (default) or server
api_accessstringNoAPI exposure: full (default) or internal
endpointsarrayYes*Ordered list of addresses; first responder wins
addressstringNoDeprecated single endpoint; prefer endpoints
fingerprintstringNoPinned TLS identity fingerprint (SHA256:...), verified before the peer is first contacted

*At least one endpoint is required: either endpoints (preferred) or the deprecated address. Each endpoint may be a bare host (10.0.0.2), host:port (10.0.0.2:9090), or a full URL (https://node-a.internal).

As of v1.4.2, scheme-less endpoints default to https, and the host's identity is verified on first contact (against the declared fingerprint, or pinned trust-on-first-use). An explicit http:// or https:// scheme is honored exactly as written; a deliberately plaintext cluster must declare explicit http:// endpoints, and apply and diff name any peer that needs it. CLI builds from before the 2026-07-22 v1.4.2 patch instead probed scheme-less endpoints and required explicit https:// on the first bootstrap (see the release notes).

fingerprint pins the peer's Ed25519 TLS identity, and is accepted with or without the SHA256: prefix. When set, compose apply and diff verify the peer presents this fingerprint before the cluster is contacted; a mismatch is always fatal and is never downgraded by --ignore-unreachable. When absent, the CLI falls back to trust-on-first-use pinning. Read the value out-of-band from the trusted host with arctic-agent fingerprint (or from the identity_fingerprint line in the agent's boot journal). See TLS and trust.

An internal peer rejects user-facing endpoints; at least one peer in the cluster must use api_access: full, or the cluster is unmanageable via the API.

Peers cannot be deleted via compose. Removing a peer from the file does not remove it from the cluster. Use arctic peers delete.

Service configuration

FieldTypeRequiredDescription
namestringYesUnique identifier for this service
source_peerstringYesName of the source peer (must exist in peers)
target_peerstringYesName of the target peer (must exist in peers)
transport_typestringYesTunnel protocol: tcp or kcp
fully_transparentboolNoFully transparent proxying mode (default false)
uplink_devstringNoEgress device this service's traffic leaves by; empty means the host's auto-detected default
interfaceobjectNoMACVLAN interface configuration
qosobjectNoQuality-of-service settings
mptcpobjectNoMultipath TCP opt-in for tunnel dials (tcp transport only)
routesarrayNoRouting rules

A service needs a routing mode: either interface with enabled: true (MACVLAN interface mode) or one or more routes (policy/CIDR routes mode). source_peer and target_peer must differ. Setting both interface (enabled) and routes on the same service is allowed but triggers an advisory warning, since you typically use one or the other.

The three device fields (uplink_dev, interface.parent_dev, routes[].device) are host-local operator intent and are not gossiped across the cluster. They pin behaviour on multi-NIC hosts, including overlay interfaces such as tailscale0 or a WireGuard device; see the uplinks guide for when to use which.

Interface configuration

FieldTypeRequiredDescription
enabledboolYesCreate a MACVLAN interface for this service
vlan_idintegerNoIEEE 802.1Q VLAN tag; valid range 1-4094 (0 and 4095 reserved). Omit for untagged
ipv4stringNoDesired IPv4 address in CIDR notation (e.g. 10.0.0.1/24). Omit for DHCP
parent_devstringNoParent device the MACVLAN is built on; empty means auto-detect the egress interface towards the target peer
macstringNoHardware address mode (see below)

mac values:

  • "" (omitted): the agent generates a MAC at runtime; it may change when the service is recreated.
  • "auto": a deterministic MAC derived from the cluster ID, source peer name, and service name. Stable across applies as long as those names do not change.
  • explicit MAC: a lowercase colon-separated hex address, e.g. 02:ab:cd:ef:12:34. Must parse cleanly.

ipv4 cannot be 0.0.0.0/0, and IPv4 addresses must be unique per source peer across services.

Omitting ipv4 requests the address over DHCP. This depends on the network answering DHCP for the interface's (previously unseen) MAC; on networks that do not, the interface comes up without an IPv4 address and cannot serve as a route target. Prefer a static ipv4 on the source peer's LAN when in doubt.

QoS configuration

bandwidth_limit_mbps applies to both transports, but differently. On a tcp service it drives the full traffic shaper (holds the rate, queues flows fairly, and keeps latency low under load). On a kcp service it acts as a bandwidth ceiling that caps the link in both directions, without the shaper's fair queueing or latency control. The remaining fields below (default_rtt_ms, disable_auto_rtt, memlimit_cap_mb) tune the TCP shaper only and have no effect on a KCP service.

FieldTypeDescription
bandwidth_limit_mbpsuint64Maximum bandwidth in Mbps; 0 means unlimited (no shaping or ceiling)
default_rtt_msuint64Seed round-trip time in milliseconds used to tune shaping; 0 uses the system default
disable_auto_rttboolDisable automatic RTT latency probing for this link
memlimit_cap_mbuint64Cap the memory the shaper may use, in megabytes; 0 means no cap

memlimit_cap_mb is useful on memory-constrained hosts to keep the shaper from allocating excessive RAM.

MPTCP configuration

Opts a tcp-transport service's tunnel dials into Multipath TCP, so one tunnel can spread across more than one path at once (it pairs naturally with the device fields above). Only valid with transport_type: tcp; enabling it on a kcp service is a validation error. Both fields are source-peer-local dial settings, like disable_auto_rtt, and are not gossiped.

FieldTypeDescription
enabledboolRequest MPTCP sockets for this service's tunnel dials
subflowsuint64Requested path-manager subflow count; 0 means the engine default (2), maximum 8

Dials fall back to plain TCP when the kernel or the remote peer lacks MPTCP support, and the fallback is silent: if throughput looks single-path, check that both peers' kernels are built with MPTCP and have net.mptcp.enabled on.

Route configuration

FieldTypeRequiredDescription
source_cidrstringConditionalSource network in CIDR notation
dest_cidrstringConditionalDestination network in CIDR notation
devicestringNoInbound device this route matches on; empty means the host's auto-detected WAN interface
priorityuint64YesTie-breaker; lower value = higher priority

At least one of source_cidr or dest_cidr is required. Both cannot be 0.0.0.0/0. Priorities must be unique within a service. device is host-local and not gossiped (see the device-field note under service configuration).

Routes are matched by specificity first, with priority only breaking ties. Specificity order, most specific first: MACVLAN interface match, source plus destination CIDR, source-only CIDR, destination-only CIDR.

Canonical key ordering

arctic compose fmt reorders keys to a canonical order:

ContextOrder
Rootversion, license, requires, server, peers, services
Peername, description, type, api_access, address, endpoints, fingerprint
Servicename, source_peer, target_peer, transport_type, fully_transparent, uplink_dev, interface, qos, mptcp, routes
Interfaceenabled, vlan_id, ipv4, parent_dev, mac
QoSbandwidth_limit_mbps, default_rtt_ms, disable_auto_rtt, memlimit_cap_mb
MPTCPenabled, subflows
Routesource_cidr, dest_cidr, device, priority
Requiresagent
Serverpeer, fallback_peer

Validation rules

Validate a file with arctic compose validate cluster.yaml. Errors block apply; warnings are advisory unless you pass --strict.

Errors

FieldDescription
versionRequired; must be v1
peersAt least one peer is required
peers[N].nameRequired and unique
peers[N].endpointsAt least one endpoint required (via endpoints or address)
peers[N].typeMust be agent or server
peers[N].api_accessMust be full or internal; at least one peer must be full
peers[N].fingerprintMust be a SHA256 fingerprint of a 32-byte digest, optionally prefixed with SHA256:
server.peerRequired when the server block is present; must reference a peer
server.fallback_peerMust differ from server.peer and reference a peer
services[N].nameRequired and unique
services[N].source_peerRequired; must reference a peer
services[N].target_peerRequired; must reference a peer; must differ from source
services[N].transport_typeRequired; must be tcp or kcp
services[N].mptcp.enabledRequires transport_type: tcp
services[N].mptcp.subflowsAt most 8 (kernel path-manager limit)
services[N]Must define interface (enabled) or routes
services[N].uplink_devValid Linux interface name: at most 15 characters; no whitespace, /, or :
services[N].interface.ipv4Valid CIDR; cannot be 0.0.0.0/0; unique per source peer
services[N].interface.vlan_idMust be in range 1-4094
services[N].interface.parent_devValid Linux interface name (same rules as uplink_dev)
services[N].interface.macValid lowercase colon-hex MAC or the literal auto
services[N].routes[M]At least one of source_cidr or dest_cidr; not both 0.0.0.0/0
services[N].routes[M].deviceValid Linux interface name (same rules as uplink_dev)
services[N].routes[M].priorityUnique within the service

Warnings

RuleFieldDescription
deprecated-addresspeers[N].addressaddress is deprecated; use endpoints
address-ignoredpeers[N].addressBoth address and endpoints set; address is ignored
interface-and-routesservices[N]Service has both interface and routes; typically use one
mac-not-locally-administeredservices[N].interface.macExplicit MAC looks vendor-burned
mac-multicastservices[N].interface.macExplicit MAC has the multicast bit set

Examples

Minimal configuration

version: v1

peers:
  - name: peer-1
    endpoints:
      - https://192.168.1.10:8080
  - name: peer-2
    endpoints:
      - https://192.168.1.20:8080

services:
  - name: tunnel-1-to-2
    source_peer: peer-1
    target_peer: peer-2
    transport_type: tcp
    routes:
      - dest_cidr: 10.0.0.0/8
        priority: 100

MACVLAN interface mode

version: v1
license: ./license.json
requires:
  agent: "^v1.4.0"

peers:
  - name: datacenter-west
    description: Primary west DC node
    endpoints:
      - https://10.0.1.10:8080
  - name: datacenter-east
    endpoints:
      - https://10.0.2.10:8080
      - https://10.0.2.11:8080

services:
  - name: west-to-east
    source_peer: datacenter-west
    target_peer: datacenter-east
    transport_type: tcp
    fully_transparent: true
    interface:
      enabled: true
      ipv4: 10.100.0.1/24
      mac: auto
    qos:
      bandwidth_limit_mbps: 100

Policy routes mode with KCP

version: v1
license: ./license.json

peers:
  - name: site-a
    endpoints:
      - https://192.168.1.10:8080
  - name: site-b
    endpoints:
      - https://192.168.2.10:8080

services:
  - name: kcp-tunnel
    source_peer: site-a
    target_peer: site-b
    transport_type: kcp
    routes:
      - source_cidr: 0.0.0.0/0
        dest_cidr: 10.20.0.0/16
        priority: 100
      - dest_cidr: 172.16.0.0/12
        priority: 200

Device pins and MPTCP

A service pinned to a second WAN interface, with MPTCP enabled so the tunnel can spread across paths. See the uplinks guide for the overlay-interface (tailscale, WireGuard) variants.

version: v1

peers:
  - name: peer-a
    endpoints:
      - https://192.168.1.10:8080
  - name: peer-b
    endpoints:
      - https://192.168.2.10:8080

services:
  - name: svc-via-wan2
    source_peer: peer-a
    target_peer: peer-b
    transport_type: tcp
    uplink_dev: ens19
    mptcp:
      enabled: true
      subflows: 2
    routes:
      - dest_cidr: 10.20.0.0/16
        priority: 100

On this page