Tillered Docs

CLI reference

Complete reference for the Arctic command-line interface

The Arctic CLI (arctic) manages Arctic agents and clusters. It follows the pattern arctic <command> [subcommand] [flags].

Most read commands map to a collection (list) or a single item (get). Output goes to stdout; progress messages, prompts, and errors go to stderr, so you can pipe machine-readable output cleanly.

bootstrap

Initialize an Arctic agent with a license file.

Synopsis

arctic bootstrap --url <agent-url> --license-file <path> [flags]

Description

Bootstrap initializes an agent with a license. This:

  1. Uploads the license to the agent
  2. Generates a unique peer identity (Ed25519 key pair)
  3. Creates OAuth2 credentials for API access
  4. Saves the cluster to your CLI config file and sets it as current

Flags

FlagDescription
--urlAgent URL (e.g. https://192.168.1.10:8080). Provide via the global --url flag
--license-filePath to the license JSON file (required)
--nameHuman-readable name for this peer (optional)
--credentials-fileWrite the break-glass admin credential pair to this JSON file (default: break-glass.<cluster-id>.json next to the CLI config, mode 0600; the literal none keeps it off disk)
--no-scoped-credentialKeep the bootstrap admin credential as the CLI working credential instead of minting a least-privilege one

Output

Peer ID:        peer_01HXYZ...
Cluster ID:     clu_01HABC...
Client ID:      cli_01HXYZ...
License ID:     lic_abc123...
Expires:        2025-12-31 23:59:59 UTC
Max Nodes:      10
Max Services:   100

Configuration saved to ~/.config/arctic/config.yaml
Set as current cluster: clu_01HABC...

Examples

# Basic bootstrap
arctic bootstrap \
  --url https://192.168.1.10:8080 \
  --license-file license.json

# With a peer name
arctic bootstrap \
  --url https://192.168.1.10:8080 \
  --license-file license.json \
  --name prod-svr-1

Notes

  • An agent can only be bootstrapped once. To re-bootstrap, reset the agent's database.
  • The license file is license.json, provided by Tillered.
  • For multi-peer clusters, prefer arctic compose apply over running bootstrap by hand.
  • As of v1.4.2, bootstrap saves a least-privilege working credential to the CLI config and treats the admin pair as break-glass: it is printed once and written to break-glass.<cluster-id>.json next to the config. Pass --no-scoped-credential to keep the old save-admin behaviour. See Credential management.

cluster

Manage cluster operations.

Synopsis

arctic cluster <subcommand> [flags]

Subcommands

SubcommandDescription
initInitialize cluster for legacy upgraded agents
identityShow cluster identity of a peer
trustVerify and pin a peer's TLS identity against an out-of-band fingerprint
syncTrigger cluster synchronization
statusShow gossip status for diagnostics

cluster init

Initialize a cluster on a legacy agent that predates cluster IDs. Required scope: urn:tillered:arctic:admin.

arctic cluster init [--force]
FlagDescription
--forceForce reinitialize even if a cluster already exists
Cluster initialized
Cluster ID:       clu_01HABC...
Created By:       peer_01HXYZ...
Oldest Peer:      peer_01HXYZ...
Was Created:      true

Notes:

  • New agents have a cluster initialized during bootstrap. This command exists for upgraded agents only.
  • --force regenerates the cluster ID and destroys existing cluster state.

cluster identity

Show the cluster identity of a peer. This is a public endpoint and requires no authentication.

arctic cluster identity
Peer ID:     peer_01KBYMHC35F0N070F3XXMEZAZR
Public Key:  qnGyPtol7lqzLYU3989eaC+K4xda0w9KXlcEOM9cSBQ=
Fingerprint: SHA256:hcJfXwZDnDEPYW26jbUcSf01ykWUlAJ031xJ9jDkzVE
License ID:  lic_01KB45EYP1VJ1RZEGJDPKJQ9V4
Cluster ID:  clu_01KBYMHMRJ92E090P50VRKG4ZC
Version:     arctic/v1.4.2

Use -j for JSON output:

{
  "peer_id": "peer_01KBYMHC35F0N070F3XXMEZAZR",
  "public_key": "qnGyPtol7lqzLYU3989eaC+K4xda0w9KXlcEOM9cSBQ=",
  "fingerprint": "SHA256:hcJfXwZDnDEPYW26jbUcSf01ykWUlAJ031xJ9jDkzVE",
  "license_id": "lic_01KB45EYP1VJ1RZEGJDPKJQ9V4",
  "cluster_id": "clu_01KBYMHMRJ92E090P50VRKG4ZC",
  "version": "v1.4.2"
}

The version field reports the agent build that answered the request, so the exact value depends on the peer you query.

As of v1.4.2, identity also shows the SHA256 fingerprint of the peer's TLS identity, derived from the advertised public key. It is shown for convenience but arrives over the (untrusted) network; to pin an identity provably, use the value from the agent's boot log with cluster trust, not this one.

cluster trust

Verify and pin a peer's TLS identity against a fingerprint read out-of-band from the trusted host. Contacts only the agent's TLS listener; requires no authentication. Added in v1.4.2; see TLS and trust.

arctic cluster trust --url <agent-address> [--fingerprint SHA256:...]
FlagDescription
--urlAgent address to probe (the global flag; required)
--fingerprintExpected SHA256 identity fingerprint to verify and pin; omit to only display the presented fingerprint

With --fingerprint, the command probes the agent's TLS identity, verifies it matches the expected value, and pins it into the known-hosts trust store; a mismatch is refused as a possible man-in-the-middle. Without --fingerprint, it prints the presented fingerprint for verification and pins nothing. The fingerprint is accepted with or without the SHA256: prefix.

Read the expected value from the trusted host itself: run arctic-agent fingerprint on the host, or read the identity_fingerprint line from the agent's boot journal (journalctl -u arctic | grep identity_fingerprint).

JSON output (-j):

{
  "host": "192.168.1.10",
  "fingerprint": "SHA256:hcJfXwZDnDEPYW26jbUcSf01ykWUlAJ031xJ9jDkzVE",
  "pinned": true
}

cluster sync

Trigger immediate cluster synchronization. Required scope: urn:tillered:arctic:admin.

arctic cluster sync

This forces a gossip round and reconciliation across the agent's reconcilers instead of waiting for the next periodic resync. Use it to speed up propagation after adding a peer or changing config.

cluster status

Show gossip status for diagnostics. Required scope: urn:tillered:arctic:admin.

arctic cluster status
Gossip Status
-------------
Local Peer:        peer_01KBYMHC35F0N070F3XXMEZAZR
Cluster ID:        clu_01KBYMHMRJ92E090P50VRKG4ZC

Peer Status:
  peer_01KBYMD32Z2RVDE3G5Y9M530QR:
    Last Seen:     2025-12-09 10:15:30 UTC
    Status:        healthy
    Registry Hash: a1b2c3d4...

Registry:
  Services:        5
  Routes:          12
  Hash:            e5f6g7h8...

Use -j for JSON output. Useful for diagnosing synchronization issues and detecting registry drift between peers.


completion

Generate shell completion scripts for the Arctic CLI.

Synopsis

arctic completion <shell>
ArgumentDescription
shellShell type: bash, zsh, or fish

Installation

# Bash: add to ~/.bashrc
source <(arctic completion bash)

# Zsh: add to ~/.zshrc
source <(arctic completion zsh)

# Fish: save to the completions directory
arctic completion fish > ~/.config/fish/completions/arctic.fish

Features

  • Command completion: arctic pe<TAB> completes to peers.
  • Subcommand completion: arctic peers l<TAB> completes to list.
  • Flag completion: arctic peers list --<TAB> lists available flags.
  • Value completion: peer IDs and service IDs complete from the local cache (see arctic cache).

Example session

$ arctic peers <TAB>
add          delete       get          list
remove-self  replace      update

$ arctic cluster <TAB>
identity  init      status    sync      trust

compose

Manage Arctic clusters declaratively from a YAML configuration file. This is the recommended way to deploy and operate a cluster.

See the compose guide for a walkthrough and the configuration reference for the full schema.

Synopsis

arctic compose <subcommand> [config-file] [flags]

Subcommands

SubcommandDescription
applyApply cluster configuration from YAML
diffShow differences between config and cluster state
rollbackRoll the cluster back to a snapshot captured by a previous apply
exportExport cluster configuration to YAML
validateValidate YAML syntax and schema
initGenerate a starter configuration template
fmtFormat a configuration file

compose apply

Apply cluster configuration from a YAML file. Required scope: urn:tillered:arctic:admin.

arctic compose apply <config-file> [flags]
FlagDefaultDescription
--dry-runfalseShow changes without applying
--prunefalseDelete resources not in config
--ignore-unreachablefalseSkip unreachable peers
--license-fileLicense file for bootstrap
--credentials-fileWrite the break-glass credential pair to this JSON file on bootstrap (default: break-glass.<cluster-id>.json next to the CLI config; the literal none keeps it off disk)
--env-fileWrite break-glass credentials to a .env file on bootstrap (opt-in)
--no-scoped-credentialfalseKeep the bootstrap admin credential as the CLI working credential instead of minting a least-privilege one
--save-configtrueSave cluster to CLI config and set as current (use --save-config=false to disable)
--skip-validatefalseSkip pre-validation checks (not recommended)
--skip-preflightfalseSkip pre-flight peer connectivity checks
--skip-requiresfalseSkip the requires block version checks
--strictfalseTreat validation warnings as errors
--backup-retention0Override preferences.backup_retention for this run (1-100; 0 uses the configured default). Retention counts snapshots: the state-plus-config pairs captured before each non-bootstrap apply
--state-dirOverride the .arctic/ state directory location
--no-lockfalseSkip cluster-wide lock acquisition (the local file lock still applies). Required when the server peer is unreachable or compose modifies server.peer
# Validate and apply
arctic compose apply cluster.yaml

# Preview changes without applying
arctic compose apply cluster.yaml --dry-run

# Apply and prune resources not in config
arctic compose apply cluster.yaml --prune

# Bootstrap and save credentials in one step
arctic compose apply cluster.yaml \
  --license-file license.json \
  --credentials-file creds.json

# Apply without saving to CLI config
arctic compose apply cluster.yaml --save-config=false

Example output:

Validating cluster.yaml...
OK - Configuration valid

Checking cluster state...
  peer-a: bootstrapped
  peer-b: bootstrapped

Planning changes...
  + Create service: web-tunnel (peer-a -> peer-b)
  + Create route: 0.0.0.0/0 -> 10.0.0.0/8

Applying changes...
  [OK] Created service web-tunnel
  [OK] Created route 10.0.0.0/8

Apply complete: 1 service created, 1 route created

Every non-bootstrap apply first captures a rollback snapshot into .arctic/backup/ (see compose rollback and state backups). Without --prune, apply also lists services that exist in the cluster but not in the config and reports them in the JSON result as a prunable array; --prune remains the single explicit opt-in for deletion.

compose diff

Show differences between the configuration file and current cluster state. Required scopes: urn:tillered:arctic:peers.read, urn:tillered:arctic:services.read.

arctic compose diff <config-file> [flags]
FlagDescription
--skip-preflightSkip pre-flight peer connectivity checks
--skip-requiresSkip the requires block version checks
--state-dirOverride the .arctic/ state directory location
Comparing cluster.yaml with cluster state...

Services:
  ~ web-tunnel: bandwidth_limit_mbps changed (0 -> 100)
  + new-tunnel: will be created
  - old-tunnel: will be deleted (with --prune)

Routes:
  + 10.0.0.0/8: will be added to web-tunnel

compose rollback

Roll the cluster back to a snapshot captured by a previous compose apply. Added in v1.4.2; see Backup and restore for the workflow.

arctic compose rollback [timestamp] [flags]

Without a timestamp the newest rollbackable snapshot is used. List snapshots with arctic state backups; both the token form (2026-07-07T07-35-39Z) and plain RFC 3339 are accepted.

FlagDefaultDescription
--dry-runfalseShow the rollback plan without applying
--backup-retention0Override preferences.backup_retention for this run (1-100; 0 uses the configured default)
--state-dirOverride the .arctic/ state directory location (default: .arctic/ in the current directory - run rollback from the project directory or pass this flag)
--no-lockfalseSkip cluster-wide lock acquisition (the local file lock still applies)

Rollback applies the captured config through the standard apply pipeline with pruning enabled, so the cluster converges back to its pre-apply configuration. Everything an apply gives you applies identically: the rendered plan, the confirmation prompt (or --yes), locks, and a fresh pre-rollback snapshot - so a rollback is itself rollbackable. Peers are never pruned; rollback's blast radius is services and routes. Snapshots without a config half (captured by versions before v1.4.2) list as non-rollbackable.

arctic compose rollback                          # newest snapshot, with prompt
arctic compose rollback 2026-07-07T07-35-39Z     # named snapshot
arctic compose rollback --dry-run                # preview only

compose export

Export the current cluster configuration to YAML. Required scopes: urn:tillered:arctic:peers.read, urn:tillered:arctic:services.read.

arctic compose export [flags]
FlagDescription
--fileOutput file path (defaults to stdout)
arctic compose export
arctic compose export --file cluster.yaml

compose validate

Validate configuration YAML syntax and schema. Runs locally; no agent connection or auth required.

arctic compose validate <config-file> [flags]
FlagDefaultDescription
--strictfalseTreat warnings as errors

Use -j for JSON output and -q to suppress non-error output (both are global flags).

Validating cluster.yaml...
OK - Configuration valid

Schema version: v1
Peers: 2
Services: 1
Routes: 1

compose init

Generate a starter configuration template.

arctic compose init [flags]
FlagDefaultDescription
--fileOutput file path (defaults to stdout)
--minimalfalseGenerate a minimal config without comments
arctic compose init                       # full example to stdout
arctic compose init --file cluster.yaml   # full example to a file
arctic compose init --minimal             # minimal config

compose fmt

Format a configuration file to canonical form. Reorders keys to the canonical order, normalizes to 2-space indentation, and preserves comments.

arctic compose fmt <config-file> [flags]
FlagDefaultDescription
--writefalseWrite formatted output back to the file
--checkfalseCheck if the file is formatted (exit 1 if not)
arctic compose fmt cluster.yaml            # print formatted config
arctic compose fmt cluster.yaml --write    # format in place
arctic compose fmt cluster.yaml --check    # CI check

Exit codes

CodeCondition
0Success
2Usage or validation error
3Configuration error
4Connection error
7Lock contention (another apply holds the lock)

config

Manage the Arctic CLI's local configuration file.

Synopsis

arctic config <subcommand> [flags]

Subcommands

SubcommandDescription
listList all clusters and peers
viewView the full configuration
currentShow the current cluster context
use-clusterSet the current cluster
add-peerAdd a peer to a cluster
delete-peerDelete a peer from the cluster configuration

config list

arctic config list
ID                    NAME         URL                           LICENSE    CURRENT
clu_01HXYZABC123...   agent-a      https://192.168.1.10:8080      lic_...    *
clu_01HXYZDEF789...   agent-b      https://192.168.1.20:8080      lic_...

config view

View the full configuration file.

arctic config view

config current

Show the current cluster context.

arctic config current
Cluster ID:      clu_01HABC...
Name:            Production
Customer ID:     cust_...
Peer Count:      3
Default Peer:    peer_01HXYZ...
Default URL:     https://192.168.1.10:8080
Format:          cluster

config use-cluster

Set the current cluster context by ID or name.

arctic config use-cluster <cluster-id-or-name>
arctic config use-cluster Production
arctic config use-cluster clu_01HABCDEF...

config add-peer

Add a peer entry to a cluster in your local config (does not contact the agent).

arctic config add-peer --peer-id <id> --peer-url <url> [flags]
FlagDescription
--peer-idPeer ID (required)
--peer-urlAgent URL (required)
--clusterCluster ID or name (defaults to current cluster)
--nameHuman-readable name for the peer
arctic config add-peer \
  --peer-id peer_01HXYZ... \
  --peer-url https://192.168.1.30:8080 \
  --name agent-c \
  --cluster Production

config delete-peer

Remove a peer entry from a cluster in your local config.

arctic config delete-peer <peer-id-or-name> [flags]
FlagDescription
--clusterCluster ID or name (defaults to current cluster)
arctic config delete-peer agent-c
arctic config delete-peer agent-c --cluster Production

Requires confirmation unless --yes is specified.


credentials

Manage OAuth2 API credentials. Alias: credential.

Changed in v1.4.2

credentials create now requires --scopes; a request without scopes is rejected instead of silently minting an admin credential. Grants are capped by the creating credential's own scopes, and admin is never granted implicitly. After a v1.4.2 bootstrap the CLI config holds a scoped working credential with no credential-management rights, so create, revoke, and rotate need the break-glass admin pair supplied via --client-id/--client-secret or ARCTIC_CLIENT_ID/ARCTIC_CLIENT_SECRET. See Credential management.

Synopsis

arctic credentials <subcommand> [flags]

Subcommands

SubcommandDescription
listList all credentials
createCreate a new credential
revokeRevoke a credential
rotateRotate the shared cluster credential secret

credentials list

List credentials for the current license. Required scope: urn:tillered:arctic:credentials.read.

arctic credentials list

Each entry shows the client ID, scopes, license ID, creation time, and last-used time. The client secret is never returned by list. Use -j for JSON output.

credentials create

Create a new credential. Required scope: urn:tillered:arctic:credentials.write (which the scoped working credential does not hold; supply the break-glass pair, see the callout above).

arctic credentials create --scopes <list>
FlagDescription
--scopesComma-separated scopes (required). Accepts full scope URNs or the aliases read, write, and admin

A credential can only be granted scopes the creating credential itself holds (an admin credential can grant anything). Omitting --scopes fails before any network call:

--scopes is required: comma-separated scopes or the aliases read, write, admin (e.g. --scopes read)

The client secret is shown once, on stderr, and cannot be retrieved again:

arctic credentials create --scopes read
arctic credentials create --scopes urn:tillered:arctic:peers.read,urn:tillered:arctic:services.read
IMPORTANT: Save the client_secret below. It will NOT be shown again.

Credential created successfully:
  Client ID:     cli_01HXYZ...
  Client Secret: sec_abc123...

  Scopes: urn:tillered:arctic:peers.read, urn:tillered:arctic:services.read

credentials revoke

Revoke a credential by client ID. Required scope: urn:tillered:arctic:credentials.write.

arctic credentials revoke <client-id>
arctic credentials revoke cli_01HXYZ...

credentials rotate

Rotate the shared cluster credential secret. The old secret keeps working for 24 hours so peers can pick up the new one via gossip. Required scope: urn:tillered:arctic:credentials.write.

arctic credentials rotate [--update-config]
FlagDescription
--update-configUpdate the CLI config file with the rotated secret
IMPORTANT: Save the new client_secret below. The old secret expires in 24 hours.

Cluster credential rotated successfully:
  Client ID:     cli_01HXYZ...
  Client Secret: sec_def456...
  Version:       2
  Rotated By:    peer_01HXYZ...

The new credential will be gossiped to all cluster peers.

database

Agent database maintenance.

Synopsis

arctic database <subcommand> [flags]

Subcommands

SubcommandDescription
snapshotTake an immediate database snapshot on the target agent

database snapshot

Take an immediate snapshot of the agent's database. Required scope: urn:tillered:arctic:cluster.write.

arctic database snapshot

The agent captures a VACUUM INTO snapshot of arctic.db into $DATA_DIR/backup/ (safe while the agent is running), bypassing the periodic schedule's freshness gate. It works even when periodic snapshots are disabled (DB_BACKUP_INTERVAL_SECONDS=0). Use it before an upgrade or right after a bootstrap; a snapshot plus peer.key restores a peer as itself after a host rebuild. See Backup and restore and the agent configuration reference.

JSON output (-j):

{
  "file_name": "arctic.2026-07-21T03-15-09Z.db",
  "path": "/opt/tillered/backup/arctic.2026-07-21T03-15-09Z.db",
  "size_bytes": 524288,
  "captured_at": "2026-07-21T03:15:09Z",
  "pruned": 1
}

pruned reports how many old snapshots were removed to honour DB_BACKUP_RETENTION.


health

Check Arctic agent health. Requires no authentication.

Synopsis

arctic health [flags]

Flags

FlagDescription
--readyzCheck readiness (/readyz) instead of liveness (/livez)
arctic health                                  # liveness, current context
arctic health --readyz                         # readiness
arctic health --url https://192.168.1.30:8080   # specific agent
arctic health -j                               # JSON output

Default output:

Peer ID:     peer_01KBYMHC35F0N070F3XXMEZAZR
Name:        agent-1
URL:         https://192.168.3.251:8080
Status:      ok
Timestamp:   2025-12-09 04:43:41 UTC

JSON output:

{
  "peer_id": "peer_01KBYMHC35F0N070F3XXMEZAZR",
  "name": "agent-1",
  "url": "https://192.168.3.251:8080",
  "status": "ok",
  "timestamp": "2025-12-09 04:43:41 UTC"
}

Liveness (/livez) indicates the process is running. Readiness (/readyz) indicates the agent is ready to serve requests, and may return not-ready during startup. Both are unauthenticated, which makes them suitable for load balancers and Kubernetes probes.

Scripting example

arctic health --url https://192.168.1.10:8080
case $? in
    0) echo "Agent healthy" ;;
    4) echo "Agent unreachable"; send_alert "Arctic agent down" ;;
    *) echo "Unexpected error" ;;
esac

license

Manage the agent license. The agent re-verifies the license on every startup.

Synopsis

arctic license <subcommand> [flags]

Subcommands

SubcommandDescription
statusShow license usage status
updateUpdate the license

license status

Show license usage status. Required scope: urn:tillered:arctic:admin.

arctic license status
License Information
-------------------
License ID:     lic_abc123...
Customer ID:    cust_xyz789...
Customer Name:  Acme Corp

Validity
--------
Issued:         2024-01-01 00:00:00 UTC
Expires:        2025-12-31 23:59:59 UTC
Status:         Valid (350 days remaining)

Limits
------
Max Nodes:      10
Max Services:   100

Metadata
--------
Issuer:         Tillered
Key ID:         2024Q4
Algorithm:      Ed25519

Use -o json for JSON output.

license update

Update the agent license with a new license file. Required scope: urn:tillered:arctic:admin.

arctic license update --license-file <path>
FlagDescription
--license-filePath to the new license file (required)
arctic license update --license-file new-license.json
License updated
Customer ID:    cust_xyz789...
License ID:     lic_def456...
Expires:        2026-12-31 23:59:59 UTC
Max Nodes:      20
Max Services:   200

Notes:

  • The new license must be for the same customer.
  • Connected peers receive the updated license via gossip.

peers

Manage cluster peers. Alias: peer.

Synopsis

arctic peers <subcommand> [flags]

Subcommands

SubcommandDescription
listList all peers
getGet details of a specific peer
addAdd a remote peer
updateUpdate peer metadata
deleteDelete a peer
replaceReplace a dead member with the rebuilt agent at its endpoint
remove-selfRemove the local peer from the cluster

peers list

List peers in the cluster. Required scope: urn:tillered:arctic:peers.read.

arctic peers list [flags]
FlagDescription
--localShow only the local peer (mutually exclusive with --remote)
--remoteShow only remote peers (mutually exclusive with --local)
arctic peers list
arctic peers list --remote
arctic peers list -j
ID                               NAME       ADDRESS
peer_01KBYMHC35F0N070F3XXMEZAZR  agent-1    192.168.3.251:8080
peer_01KBYMD32Z2RVDE3G5Y9M530QR  agent-2    10.10.40.95:8080

peers get

Get a specific peer by ID. Required scope: urn:tillered:arctic:peers.read.

arctic peers get <peer-id>
ID:          peer_01KBYMHC35F0N070F3XXMEZAZR
Name:        agent-1
Public Key:  qnGyPtol7lqzLYU3989eaC+K4xda0w9KXlcEOM9cSBQ=
Is Local:    Yes
Address:     192.168.3.251:8080
Created At:  2025-12-08 09:26:30 UTC

peers add

Add a remote peer to the cluster. Required scope: urn:tillered:arctic:peers.write.

arctic peers add <peer-address> [flags]
FlagDescription
--nameHuman-readable name for the peer
arctic peers add 192.168.1.20:8080
arctic peers add 192.168.1.20:8080 --name prod-svr

This contacts the remote peer, performs a handshake (exchanging public keys), verifies both signatures against the license, then records the peer locally. The remote peer adds the local peer via gossip.

peers update

Update a peer's metadata. Required scope: urn:tillered:arctic:peers.write.

arctic peers update <peer-id> [flags]
FlagDefaultDescription
--nameHuman-readable name for the peer
--qostrueEnable QoS for this peer (use --qos=false to disable)
arctic peers update peer_01HXYZ... --name prod-svr-2
arctic peers update peer_01HXYZ... --qos=false

peers delete

Delete a remote peer. Required scope: urn:tillered:arctic:peers.write.

arctic peers delete <peer-id> [--yes]
arctic peers delete peer_01HXYZ...
arctic peers delete peer_01HXYZ... --yes

To remove the local peer, use remove-self.

peers replace

Replace a dead member with the rebuilt agent now running on its endpoint: delete the old member (which also deletes the services still bound to it), then re-apply the compose config so the fresh agent joins under the same name and the services are recreated. This is the one-step repair for a host that was rebuilt with only peer.key (see Backup and restore). Required scopes: urn:tillered:arctic:peers.read, urn:tillered:arctic:peers.write, urn:tillered:arctic:services.read, plus whatever the embedded apply needs.

arctic peers replace <peer-id-or-name> <config.yaml> [flags]
FlagDescription
--dry-runShow what the replace would do without changing anything
arctic peers replace peer_01HXYZ... ./cluster.yaml --dry-run
arctic peers replace prod-svr-2 ./cluster.yaml --yes

The command refuses when the agent behind the member's endpoint is healthy (nothing to replace), belongs to a different cluster, or is unreachable, and when the name is ambiguous (pass the member ID). With -o json it emits a single document containing the replace summary and the embedded apply result.

peers remove-self

Gracefully remove the local peer from the cluster. Required scope: urn:tillered:arctic:admin.

arctic peers remove-self [--yes]

This deactivates the local peer and broadcasts the deactivation so other peers drop it from their databases. Services involving this peer become inactive.


routes

Manage CIDR-based routing rules for services. Alias: route.

Synopsis

arctic routes <subcommand> --service <service-id> [flags]

Subcommands

SubcommandDescription
listList routes for a service
addAdd a route to a service
updateUpdate a route
deleteDelete a route

routes list

List routes for a service. Required scope: urn:tillered:arctic:services.read.

arctic routes list --service <service-id>
ID      PRIORITY  SOURCE CIDR           DEST CIDR
6       100       10.0.0.0/8            192.168.100.0/24
7       200       10.1.0.0/16           192.168.200.0/24

routes add

Add a route to a service. Required scope: urn:tillered:arctic:services.write.

arctic routes add --service <service-id> [flags]
FlagDescription
--serviceService ID (required)
--priorityRoute priority (lower value = higher priority)
--source-cidrSource CIDR (e.g. 10.0.0.0/8)
--dest-cidrDestination CIDR (e.g. 192.168.100.0/24)

At least one of --source-cidr or --dest-cidr is required.

# Source and destination CIDR
arctic routes add --service svc_01HSVCABC123... \
  --priority 100 \
  --source-cidr 10.0.0.0/8 \
  --dest-cidr 192.168.100.0/24

# Destination only
arctic routes add --service svc_01HSVCABC123... \
  --priority 100 \
  --dest-cidr 192.168.100.0/24

routes update

Update a route. Required scope: urn:tillered:arctic:services.write.

arctic routes update --service <service-id> --route <route-id> [flags]
FlagDescription
--serviceService ID (required)
--routeRoute ID (required)
--priorityNew priority
--source-cidrNew source CIDR
--dest-cidrNew destination CIDR
arctic routes update --service svc_01HSVCABC123... --route 6 --priority 200

routes delete

Delete a route. Required scope: urn:tillered:arctic:services.write.

arctic routes delete --service <service-id> --route <route-id> [--yes]
arctic routes delete --service svc_01HSVCABC123... --route 6 --yes

Route priority and specificity

Routes are matched by specificity first, then priority breaks ties. Specificity order, most specific first: MACVLAN interface match, source plus destination CIDR, source-only CIDR, destination-only CIDR. When two routes have equal specificity, the lower --priority value wins.

PrioritySource CIDRDest CIDROrder
20010.1.0.0/16192.168.100.0/241st (source + dest)
10010.0.0.0/8(none)2nd (source only)
100(none)192.168.0.0/163rd (dest only)

services

Manage services for network routing. Alias: service.

Synopsis

arctic services <subcommand> [flags]

Subcommands

SubcommandDescription
listList services
getGet details of a specific service
createCreate a new service
updateUpdate service settings
deleteDelete a service

services list

List services. Required scope: urn:tillered:arctic:services.read.

arctic services list [flags]
FlagDescription
--source-peerFilter by source peer ID
--target-peerFilter by target peer ID
--requires-interfaceFilter to services requiring a MACVLAN interface
arctic services list
arctic services list --target-peer peer_01HXYZ...
arctic services list -j

Table output is a compact view with a per-service flags column:

ID                              SOURCE          TARGET          TRANSPORT  FLAGS
svc_01KBYMNEVYN9NC4M3RB3ZGN4RZ  agent-1         agent-2         tcp        I
svc_01KBYMP88A7MBM1KTXBYSFA7VK  agent-2         agent-1         tcp        IT

FLAGS: I=interface, T=transparent, Q=qos, -=none

Use -j or -o json for full detail with all fields.

services get

Get a specific service by ID. Required scope: urn:tillered:arctic:services.read.

arctic services get <service-id>
ID:                     svc_01KBYMNEVYN9NC4M3RB3ZGN4RZ
Source Peer ID:         peer_01KBYMHC35F0N070F3XXMEZAZR
Source Peer Name:       agent-1
Target Peer ID:         peer_01KBYMD32Z2RVDE3G5Y9M530QR
Target Peer Name:       agent-2
Transport:              tcp
Requires Interface:     Yes
Interface MAC:          4a:cc:db:76:cf:59
Actual Interface IPv4:  192.168.3.219
Fully Transparent:      No
Created At:             2025-12-08 09:28:44 UTC
Updated At:             2025-12-08 09:28:44 UTC
Data Source:            source

If the source peer is unreachable, the CLI queries the target peer for service data. Interface details may then be incomplete and Data Source: local is shown.

services create

Create a new service. Required scope: urn:tillered:arctic:services.write.

arctic services create --target-peer <id> [flags]
FlagDefaultDescription
--target-peerTarget peer ID (required)
--source-peerlocal peerSource peer ID
--transporttcpTransport type: tcp or kcp
--requires-interfacefalseCreate a MACVLAN interface
--desired-ipv4IPv4 CIDR for the interface
--interface-parent-devParent device for the MACVLAN interface (empty = auto-detect)
--uplink-devEgress device for this service's traffic (empty = auto-detect)
--fully-transparentfalseEnable fully transparent mode
--bandwidth-limit0Bandwidth limit in Mbps (0 = unlimited)
--mptcpfalseEnable MPTCP for tunnel dials (tcp transport only; rejected with kcp)
--mptcp-subflows0Requested MPTCP subflows per connection (0 = engine default of 2, max 8)

The device pins and the MPTCP settings are local to the source peer and are not gossiped; see the uplinks guide.

arctic services create --target-peer peer_01HXYZ...

arctic services create \
  --target-peer peer_01HXYZ... \
  --requires-interface \
  --desired-ipv4 192.168.100.10/24

arctic services create \
  --target-peer peer_01HXYZ... \
  --transport kcp \
  --bandwidth-limit 1000

arctic services create \
  --target-peer peer_01HXYZ... \
  --uplink-dev ens19 \
  --mptcp

services update

Update a service's settings. Required scope: urn:tillered:arctic:services.write.

arctic services update <service-id> [flags]
FlagDescription
--requires-interface[=BOOL]Set the MACVLAN interface requirement
--fully-transparent[=BOOL]Set fully transparent mode
--bandwidth-limitBandwidth limit in Mbps (0 = unlimited)
--desired-ipv4IPv4 CIDR for the interface
--uplink-devSet the egress device for this service (pass an empty value to clear and return to auto-detect)
--interface-parent-devSet the MACVLAN parent device (an empty value clears)
--mptcp[=BOOL]Set MPTCP for tunnel dials (--mptcp=false disables)
--mptcp-subflowsRequested MPTCP subflows per connection (0 = engine default of 2, max 8)
arctic services update svc_01HSVCABC123... --requires-interface
arctic services update svc_01HSVCABC123... --requires-interface=false
arctic services update svc_01HSVCABC123... --bandwidth-limit 500
arctic services update svc_01HSVCABC123... --uplink-dev tailscale0
arctic services update svc_01HSVCABC123... --uplink-dev ""

The device pins are updatable without recreating the service, and both fields show in the services get output when set. Mixed-version caveat: a not-yet-upgraded source peer ignores the device fields on forwarded updates; re-apply after completing the fleet upgrade.

services delete

Delete a service. Required scope: urn:tillered:arctic:services.write.

arctic services delete <service-id> [--yes]

Deleting a service removes its routes and MACVLAN interface (if any) and updates the firewall and data-plane state.


cache

Manage the local completion cache. Shell completion uses this cache to suggest peer and service IDs.

Synopsis

arctic cache <subcommand>

Subcommands

SubcommandDescription
refreshFetch peers and services from the cluster and update the cache
statusShow cache location, age, and contents
deleteDelete the cache file

cache refresh

arctic cache refresh
Cache updated: 2 success, 0 failed (84ms)

cache status

arctic cache status
Cache Location: ~/.config/arctic/cache.json
Last Updated:   2025-12-09 04:43:41 UTC (12m ago)
TTL:            1h

Cluster: clu_01HABC...
  Peers:    3
  Services: 5

If the cache is older than its TTL, a hint to run arctic cache refresh is printed.

cache delete

arctic cache delete [--yes]

Prompts for confirmation unless --yes is set.


state

Manage the local .arctic/ working-directory state used by compose apply.

Synopsis

arctic state <subcommand> [flags]

Subcommands

SubcommandDescription
backupsList rollback snapshots captured by compose apply
unlockRelease stale local and cluster locks

state backups

List the rollback snapshots under .arctic/backup/. Runs locally; no agent connection or auth required. Each snapshot is a pair captured by a non-bootstrap compose apply just before plan execution: a copy of the pre-apply cluster.state plus a config export of the pre-apply cluster. arctic compose rollback consumes the pair.

arctic state backups [flags]
FlagDescription
--state-dirOverride the .arctic/ state directory location
TIMESTAMP             ROLLBACK  TOKEN
2026-07-21T03:15:09Z  yes       2026-07-21T03-15-09Z
2026-07-14T11:02:44Z  no        2026-07-14T11-02-44Z

ROLLBACK is yes when the snapshot has its config half; state-only backups written by versions before v1.4.2 list as no and cannot be rolled back to. Pass the TOKEN value to arctic compose rollback.

With --output json:

[
  {
    "timestamp": "2026-07-21T03:15:09Z",
    "token": "2026-07-21T03-15-09Z",
    "state_file": ".arctic/backup/cluster.2026-07-21T03-15-09Z.state",
    "config_file": ".arctic/backup/cluster.2026-07-21T03-15-09Z.yaml",
    "rollback": true
  }
]

state unlock

Release a stale lock left behind by an interrupted compose apply. The lock ID is the positional argument and comes from the contention error message.

arctic state unlock <lock-id> [flags]
FlagDescription
--state-dirOverride the .arctic/ state directory location
--forceAdmin override on the cluster tier; bypasses the holder-id check when releasing the cluster lock
--cluster-onlySkip the local file lock; only release the cluster lock
--local-onlySkip the cluster lock; only release the local file lock
arctic state unlock 01HXYZLOCK...
Released local lock 01HXYZLOCK...

If the lock ID does not match the recorded holder, the command reports a mismatch and exits with a usage error unless --force is supplied on the cluster tier.


mcp

Start a Model Context Protocol (MCP) stdio server so an LLM agent can drive cluster management. The server reads and writes MCP messages over stdio.

Synopsis

arctic mcp [flags]

Flags

FlagDefaultDescription
--allow-writefalseEnable write tools (e.g. compose apply). Omit to restrict to read-only tools

By default only read-only tools are exposed. Pass --allow-write to permit mutating operations.


Global flags

These flags work with any command.

Context and connection

FlagDescription
--context, -cContext to use (peer ID, peer name, cluster ID, or cluster name)
--urlOverride the agent URL for this command
--tokenUse a specific bearer token instead of configured credentials
--client-idOverride the OAuth client ID (e.g. cli_01HXYZ...)
--client-secretOverride the OAuth client secret (e.g. sec_...)
--insecureConnect over TLS without verifying the pinned agent identity, and accept and re-learn a changed identity
--known-hostsPath to the trust-on-first-use known-hosts file (default: known_hosts.yaml in the user config dir)
--project-fileCompose file for project-scope target resolution (default: cluster.yaml in the current directory; the literal none disables it)
--recovery-tokenRecovery token value (sent as the X-Arctic-Recovery header)
--recovery-token-filePath to a file containing the recovery token
arctic peers list --context prod-cluster
arctic peers list --client-id cli_01HXYZ... --client-secret sec_abc123...

--client-id/--client-secret (and their environment fallbacks) override the config credential for every command as of v1.4.2 - this is how the break-glass admin pair is used for admin-only operations without ever writing it to the config.

--insecure is not a routine or testing-only option: it is the deliberate trust-on-first-use path for the moments when no pinned identity can match - bootstrapping trust with a new cluster, re-learning an identity after a key rotation, or repairing a corrupt trust store. It forces https, learns the presented identities, and pins them for subsequent verified connections. See TLS and trust.

As of v1.4.2 the trust store no longer follows --config: the default known_hosts.yaml location is always the user config home, independent of a custom config path. Setups that kept a co-located trust store in a custom config directory should pass --known-hosts (or set ARCTIC_KNOWN_HOSTS) to keep using that file.

Target resolution

With no explicit target, commands resolve which cluster to talk to in this order, highest precedence first:

  1. Explicit --url or --context flags (or their environment variables)
  2. The project directory: cluster.yaml in the current directory (override the file with --project-file / ARCTIC_PROJECT_FILE; the literal none disables project resolution)
  3. The ambient current-cluster context from the CLI config

In a project directory, the project wins over the ambient context. The compose file supplies endpoints and declared fingerprints (a declared fingerprint is verified by a live probe before the peer is first contacted; a mismatch aborts), and credentials are matched from the CLI config by the cluster's live identity. cluster.yaml plus ARCTIC_CLIENT_ID/ARCTIC_CLIENT_SECRET is a complete CI setup with no config file at all.

Output control

FlagDescription
--output, -oOutput format: table, json, or yaml
--json, -jShorthand for -o json
--no-headersOmit column headers in table output
--quiet, -qSuppress non-error output

Table format is a compact view for list commands. Use -j or -o json for full detail. Errors always go to stderr regardless of output format.

Confirmation and debugging

FlagDescription
--yes, -ySkip confirmation prompts for destructive operations
--debug, --verbose, -vEnable debug logging
--traceShow full HTTP request and response traces
--timeoutRequest timeout (e.g. 30s, 1m30s)
--configPath to the config file (default ~/.config/arctic/config.yaml)

Environment variables

FlagEnvironment variable
--configARCTIC_CONFIG
--contextARCTIC_CONTEXT
--urlARCTIC_URL
--tokenARCTIC_TOKEN
--client-idARCTIC_CLIENT_ID
--client-secretARCTIC_CLIENT_SECRET
--known-hostsARCTIC_KNOWN_HOSTS
--project-fileARCTIC_PROJECT_FILE
--recovery-tokenARCTIC_RECOVERY_TOKEN

ARCTIC_CLIENT_ID/ARCTIC_CLIENT_SECRET override the config credential for every command, the same as the flags. Agent-side environment variables (DATA_DIR, PEER_TLS_ENABLED, ...) are documented in the agent configuration reference.


Exit codes

CodeNameDescription
0SuccessCommand completed successfully
1General errorUnspecified error
2Usage errorInvalid arguments, unknown flags, or a missing required value
3Config errorMissing or invalid configuration
4Connection errorAgent could not be reached
5Auth errorInvalid or expired credentials
6Partial failureSome operations succeeded, others failed
7Lock contentionA compose apply lock is held by another process
130InterruptedThe operation was cancelled (Ctrl+C) at a prompt

Exit code 3 also covers two fail-closed cases added in v1.4.2:

  • A corrupt or unreadable known_hosts.yaml trust store. The CLI exits naming the file rather than downgrading connections to unpinned TLS; --insecure is the deliberate unpinned override and the recovery path for repairing the store.
  • A downgraded CLI reading a .arctic/cluster.state file written by a newer version (schema v2). The message names both schema versions; remove .arctic/ to re-bootstrap the local state with the older CLI.

Exit codes are independent of output format. Error messages are always written to stderr.

arctic health --url https://192.168.1.10:8080
case $? in
    0) echo "healthy" ;;
    4) echo "unreachable" ;;
    5) echo "auth failed" ;;
    *) echo "error" ;;
esac

On this page