Tillered Docs

Compose

Manage your Arctic cluster using YAML configuration files

This guide explains how to use the arctic compose command to manage your cluster declaratively using YAML configuration files.

Configuration Builder

Use the Configuration Builder to visually define peers and services, then export a ready-to-use cluster.yaml.

Overview

The compose command is the recommended way to deploy and manage Arctic clusters. It provides an Infrastructure as Code (IaC) approach where you define your desired cluster state in a YAML file and apply it declaratively.

Benefits

  • Version Control: Track cluster configuration changes in Git
  • Reproducibility: Deploy identical configurations across environments
  • Review Process: Use pull requests to review changes before applying
  • Automation: Integrate with CI/CD pipelines

When to Use Compose vs Imperative Commands

ScenarioRecommended Approach
Initial cluster setupcompose apply
Managing multiple environmentscompose apply
Production deploymentscompose apply
CI/CD deploymentscompose apply
Quick one-off changesImperative commands
Debugging/explorationImperative commands

Basic Workflow

The typical workflow for using compose is:

  1. Init a starter configuration (for new clusters)
  2. Export existing configuration (if upgrading from imperative management)
  3. Edit the YAML file to define desired state
  4. Format the configuration for consistency
  5. Validate the configuration
  6. Diff to preview changes
  7. Apply to make changes
# Create starter config (new clusters)
arctic compose init --file cluster.yaml

# Or export existing state (existing clusters)
arctic compose export --file cluster.yaml

# Edit cluster.yaml as needed

# Format after editing (optional but recommended)
arctic compose fmt cluster.yaml --write

# Validate the config
arctic compose validate cluster.yaml

# Preview changes
arctic compose diff cluster.yaml

# Apply changes
arctic compose apply cluster.yaml

Configuration File Structure

The schema version is v1. The top-level keys, in canonical order, are version, license, requires, server, peers, and services.

Minimal Example

version: v1

peers:
  - name: agent-1
    endpoints:
      - 192.168.1.10:8080
  - name: agent-2
    endpoints:
      - 192.168.1.20:8080

services:
  - name: tunnel-1-to-2
    source_peer: agent-1
    target_peer: agent-2
    transport_type: tcp
    routes:
      - source_cidr: 0.0.0.0/0
        dest_cidr: 10.0.0.0/8
        priority: 100

A service needs transport_type and either an interface block or routes to route traffic.

Peers cannot be deleted via compose

Removing a peer from the configuration file does not remove it from the cluster. Use arctic peers delete (or the API) to remove peers.

Full Example

version: v1
license: ./license.json

requires:
  agent: "^v1.4.0"

server:
  peer: datacenter-west
  fallback_peer: datacenter-east

peers:
  - name: datacenter-west
    description: Primary west DC agent
    type: agent
    api_access: full
    endpoints:
      - https://west.internal:8080
      - 10.0.1.10:8080
  - name: datacenter-east
    type: agent
    endpoints:
      - 10.0.2.10:8080

services:
  - name: west-to-east
    source_peer: datacenter-west
    target_peer: datacenter-east
    transport_type: tcp
    fully_transparent: true
    interface:
      enabled: true
      vlan_id: 100
      ipv4: 10.100.0.1/24
      mac: auto
    qos:
      bandwidth_limit_mbps: 100
      default_rtt_ms: 30
      disable_auto_rtt: false
      memlimit_cap_mb: 64
    routes:
      - source_cidr: 0.0.0.0/0
        dest_cidr: 10.0.2.0/24
        priority: 100

  - name: east-to-west
    source_peer: datacenter-east
    target_peer: datacenter-west
    transport_type: tcp
    fully_transparent: true
    routes:
      - source_cidr: 0.0.0.0/0
        dest_cidr: 10.0.1.0/24
        priority: 100

Top-level Fields

FieldRequiredDescription
versionyesSchema version. Currently v1.
licensenoRelative path to the license file (resolved from the config file location). Used for bootstrap when all peers are unbootstrapped.
requiresnoVersion constraints every peer must satisfy. See below.
servernoDesignated server peer for centralized operations. Absent means fully decentralized. See below.
peersyesPeers (nodes) in the cluster.
servicesyesServices (tunnels) between peers.

requires

Declares version constraints that every peer in the cluster must satisfy. The constraint is checked after pre-flight connectivity and before diff or apply.

requires:
  agent: "^v1.4.0"
SyntaxMeaning
vX.Y.ZExact version.
~vX.Y.ZPatch range (any vX.Y.* at or above the given patch).
^vX.Y.ZMinor range (any vX.* at or above the given minor).

server

When present, apply targets this peer (with an optional single fallback) for all authenticated requests. Omitting the block keeps Arctic in fully decentralized mode.

server:
  peer: datacenter-west
  fallback_peer: datacenter-east
  features:
    webui: false
    stun: false
FieldRequiredDescription
peeryesName of the primary server peer. Must reference a peer in peers.
fallback_peernoSingle fallback used when the primary is unreachable at connection level. This is a recovery path, not a load balancer; lock state held by the primary is not visible from the fallback.
features.webuinoReserved in v1.
features.stunnoReserved in v1.

peers

Each peer is a node in the cluster. Canonical field order: name, description, type, api_access, address (deprecated), endpoints.

FieldRequiredDescription
nameyesUnique human-readable identifier.
descriptionnoFree-form description shown in operator output. Not gossiped, no routing effect.
typenoPeer role: agent (default) or server.
api_accessnoAPI exposure: full (default) or internal. Internal-only peers reject user-facing endpoints and are reachable via the agent's recovery token only.
endpointsyesOrdered list of addresses. The CLI tries them in order; the first to respond wins. Each entry may be a bare host (10.0.0.2), host:port (10.0.0.2:9090), or a full URL (https://node-a.internal).

address is deprecated

The legacy single-value address field still parses, but new configs should use endpoints. If both are set, address is ignored and the linter emits a deprecation warning.

services

Each service is a tunnel between two peers. Canonical field order: name, source_peer, target_peer, transport_type, fully_transparent, interface, qos, routes.

FieldRequiredDescription
nameyesUnique human-readable identifier.
source_peeryesPeer name where the service originates.
target_peeryesPeer name where the service terminates.
transport_typeyesTunnel protocol: tcp or kcp.
fully_transparentnoEnables fully transparent proxying mode.
interfacenoMACVLAN interface configuration. See below.
qosnoQuality of service settings. See below.
routesnoPolicy / CIDR routing rules. See below.

A service uses one of two routing modes: a MACVLAN interface (interface.enabled: true) or policy/CIDR routes.

interface

Canonical field order: enabled, vlan_id, ipv4, mac.

FieldRequiredDescription
enabledyesEnables MACVLAN interface creation for this service.
vlan_idnoIEEE 802.1Q VLAN tag, 1-4094. Omit (or 0) for no VLAN tagging.
ipv4noDesired IPv4 address in CIDR notation. Omit for DHCP.
macnoHardware address. "" lets the agent pick at runtime (may change across recreations); auto derives a deterministic MAC from cluster ID, source peer, and service name; or an explicit colon-separated lowercase hex MAC (aa:bb:cc:dd:ee:ff).

qos

Canonical field order: bandwidth_limit_mbps, default_rtt_ms, disable_auto_rtt, memlimit_cap_mb.

FieldRequiredDescription
bandwidth_limit_mbpsnoMaximum bandwidth in Mbps. 0 disables shaping and RTT probing for this link.
default_rtt_msnoSeed round-trip time in milliseconds used to tune shaping (0 uses the system default).
disable_auto_rttnoDisables RTT latency probing for this link when true.
memlimit_cap_mbnoCaps the calculated shaper memlimit in megabytes (0 disables the cap). Use on memory-constrained hosts.

routes

Canonical field order: source_cidr, dest_cidr, priority.

FieldRequiredDescription
source_cidrnoSource network in CIDR notation.
dest_cidrnoDestination network in CIDR notation.
priorityyesTie-breaker order. A lower value is higher priority.

Routes are evaluated by specificity first (MACVLAN interface match > source+dest CIDR > source CIDR > dest CIDR), and priority only breaks ties between routes of equal specificity.

Validating Configuration

Always validate your configuration before applying:

arctic compose validate cluster.yaml

Validation runs parse, then schema validation, then lint. Lint warnings pass by default; pass --strict to treat warnings as errors. The command supports --json for machine-readable output and --quiet to show errors only.

Previewing Changes

Use diff to see what will change before applying:

arctic compose diff cluster.yaml

The diff legend is:

  • + create
  • - delete (shown with --prune)
  • ~ modify

Applying Configuration

Basic Apply

arctic compose apply cluster.yaml

This will:

  1. Run pre-validation (lint)
  2. Check pre-flight connectivity to each peer
  3. Enforce any requires constraints
  4. Show planned changes
  5. Prompt for confirmation
  6. Apply changes

Apply Flags

FlagDefaultDescription
--dry-runfalseShow changes without applying.
--prunefalseDelete resources not present in the config.
--ignore-unreachablefalseSkip peers that cannot be contacted.
--license-file <path>-License file used for bootstrap.
--credentials-file <path>-Write bootstrap credentials to a JSON file.
--env-file <path>-Write bootstrap credentials to a .env file.
--save-configtrueSave the cluster to the CLI config and set it as current. Use --save-config=false to disable.
--skip-validatefalseSkip pre-validation checks.
--strictfalseTreat validation warnings as errors.
--no-lockfalseSkip the cluster-wide apply lock (the local lock still applies).
--skip-preflightfalseSkip the pre-apply reachability check of peers.
--skip-requiresfalseSkip the requires.agent version check.
--state-dir <path>.arcticDirectory for the local state cache, lock, and backups.
--backup-retention <n>5Number of state backups to keep (1-100).

Dry Run

Preview changes without applying:

arctic compose apply cluster.yaml --dry-run

Prune Orphaned Resources

Delete services not defined in the configuration:

arctic compose apply cluster.yaml --prune

Use --prune with caution. Review the plan with --dry-run first. Note that pruning never deletes peers.

Handle Unreachable Peers

Skip peers that cannot be contacted:

arctic compose apply cluster.yaml --ignore-unreachable

Bootstrap with License

When all peers are unbootstrapped, the first peer is bootstrapped using the license. Provide the license via --license-file or the top-level license key, and capture the generated credentials:

arctic compose apply cluster.yaml \
  --license-file license.json \
  --credentials-file creds.json

The credentials file contains the OAuth client ID (cli_...) and secret (sec_...) for the bootstrapped cluster. Store it securely; the secret cannot be retrieved later.

Exporting Configuration

Export current cluster state to YAML:

# To stdout
arctic compose export

# To file
arctic compose export --file cluster.yaml

This is useful for:

  • Migrating from imperative to declarative management
  • Creating backups
  • Starting a new configuration from existing state

The license field is left blank on export; fill it in manually before applying to a fresh cluster.

Best Practices

Use Version Control

Store your configuration files in Git:

git add cluster.yaml
git commit -m "Add west-to-east tunnel service"

Review Before Applying

Always use diff and --dry-run before applying to production:

# First, see the diff
arctic compose diff production.yaml

# Then, dry run
arctic compose apply production.yaml --dry-run

# Finally, apply
arctic compose apply production.yaml

Credential Management

When using --credentials-file or --env-file:

  1. Never commit credentials to version control
  2. Use secure storage (e.g. a secrets manager)

Troubleshooting

Validation Errors

ERROR - Configuration invalid
Errors:
  - services[0]: source_peer "unknown" not found in peers list

Fix: Ensure all peer references match peer names defined in the peers section.

Connection Errors

Error: failed to connect to peer agent-2: connection refused

Options:

  • Verify the peer endpoints are correct
  • Check network connectivity
  • Use --ignore-unreachable to skip

Drift Detection

If the cluster state has drifted from your configuration:

# See differences
arctic compose diff cluster.yaml

# Re-apply to restore desired state
arctic compose apply cluster.yaml

See Also

On this page