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:
- Uploads the license to the agent
- Generates a unique peer identity (Ed25519 key pair)
- Creates OAuth2 credentials for API access
- Saves the cluster to your CLI config file and sets it as current
Flags
| Flag | Description |
|---|---|
--url | Agent URL (e.g. https://192.168.1.10:8080). Provide via the global --url flag |
--license-file | Path to the license JSON file (required) |
--name | Human-readable name for this peer (optional) |
--credentials-file | Write 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-credential | Keep 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-1Notes
- 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 applyover 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>.jsonnext to the config. Pass--no-scoped-credentialto keep the old save-admin behaviour. See Credential management.
cluster
Manage cluster operations.
Synopsis
arctic cluster <subcommand> [flags]Subcommands
| Subcommand | Description |
|---|---|
init | Initialize cluster for legacy upgraded agents |
identity | Show cluster identity of a peer |
trust | Verify and pin a peer's TLS identity against an out-of-band fingerprint |
sync | Trigger cluster synchronization |
status | Show 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]| Flag | Description |
|---|---|
--force | Force reinitialize even if a cluster already exists |
Cluster initialized
Cluster ID: clu_01HABC...
Created By: peer_01HXYZ...
Oldest Peer: peer_01HXYZ...
Was Created: trueNotes:
- New agents have a cluster initialized during bootstrap. This command exists for upgraded agents only.
--forceregenerates 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 identityPeer ID: peer_01KBYMHC35F0N070F3XXMEZAZR
Public Key: qnGyPtol7lqzLYU3989eaC+K4xda0w9KXlcEOM9cSBQ=
Fingerprint: SHA256:hcJfXwZDnDEPYW26jbUcSf01ykWUlAJ031xJ9jDkzVE
License ID: lic_01KB45EYP1VJ1RZEGJDPKJQ9V4
Cluster ID: clu_01KBYMHMRJ92E090P50VRKG4ZC
Version: arctic/v1.4.2Use -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:...]| Flag | Description |
|---|---|
--url | Agent address to probe (the global flag; required) |
--fingerprint | Expected 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 syncThis 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 statusGossip 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>| Argument | Description |
|---|---|
shell | Shell 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.fishFeatures
- Command completion:
arctic pe<TAB>completes topeers. - Subcommand completion:
arctic peers l<TAB>completes tolist. - 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 trustcompose
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
| Subcommand | Description |
|---|---|
apply | Apply cluster configuration from YAML |
diff | Show differences between config and cluster state |
rollback | Roll the cluster back to a snapshot captured by a previous apply |
export | Export cluster configuration to YAML |
validate | Validate YAML syntax and schema |
init | Generate a starter configuration template |
fmt | Format a configuration file |
compose apply
Apply cluster configuration from a YAML file. Required scope: urn:tillered:arctic:admin.
arctic compose apply <config-file> [flags]| Flag | Default | Description |
|---|---|---|
--dry-run | false | Show changes without applying |
--prune | false | Delete resources not in config |
--ignore-unreachable | false | Skip unreachable peers |
--license-file | License file for bootstrap | |
--credentials-file | Write 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-file | Write break-glass credentials to a .env file on bootstrap (opt-in) | |
--no-scoped-credential | false | Keep the bootstrap admin credential as the CLI working credential instead of minting a least-privilege one |
--save-config | true | Save cluster to CLI config and set as current (use --save-config=false to disable) |
--skip-validate | false | Skip pre-validation checks (not recommended) |
--skip-preflight | false | Skip pre-flight peer connectivity checks |
--skip-requires | false | Skip the requires block version checks |
--strict | false | Treat validation warnings as errors |
--backup-retention | 0 | Override 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-dir | Override the .arctic/ state directory location | |
--no-lock | false | Skip 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=falseExample 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 createdEvery 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]| Flag | Description |
|---|---|
--skip-preflight | Skip pre-flight peer connectivity checks |
--skip-requires | Skip the requires block version checks |
--state-dir | Override 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-tunnelcompose 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.
| Flag | Default | Description |
|---|---|---|
--dry-run | false | Show the rollback plan without applying |
--backup-retention | 0 | Override preferences.backup_retention for this run (1-100; 0 uses the configured default) |
--state-dir | Override the .arctic/ state directory location (default: .arctic/ in the current directory - run rollback from the project directory or pass this flag) | |
--no-lock | false | Skip 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 onlycompose export
Export the current cluster configuration to YAML. Required scopes: urn:tillered:arctic:peers.read, urn:tillered:arctic:services.read.
arctic compose export [flags]| Flag | Description |
|---|---|
--file | Output file path (defaults to stdout) |
arctic compose export
arctic compose export --file cluster.yamlcompose validate
Validate configuration YAML syntax and schema. Runs locally; no agent connection or auth required.
arctic compose validate <config-file> [flags]| Flag | Default | Description |
|---|---|---|
--strict | false | Treat 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: 1compose init
Generate a starter configuration template.
arctic compose init [flags]| Flag | Default | Description |
|---|---|---|
--file | Output file path (defaults to stdout) | |
--minimal | false | Generate 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 configcompose 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]| Flag | Default | Description |
|---|---|---|
--write | false | Write formatted output back to the file |
--check | false | Check 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 checkExit codes
| Code | Condition |
|---|---|
| 0 | Success |
| 2 | Usage or validation error |
| 3 | Configuration error |
| 4 | Connection error |
| 7 | Lock contention (another apply holds the lock) |
config
Manage the Arctic CLI's local configuration file.
Synopsis
arctic config <subcommand> [flags]Subcommands
| Subcommand | Description |
|---|---|
list | List all clusters and peers |
view | View the full configuration |
current | Show the current cluster context |
use-cluster | Set the current cluster |
add-peer | Add a peer to a cluster |
delete-peer | Delete a peer from the cluster configuration |
config list
arctic config listID 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 viewconfig current
Show the current cluster context.
arctic config currentCluster ID: clu_01HABC...
Name: Production
Customer ID: cust_...
Peer Count: 3
Default Peer: peer_01HXYZ...
Default URL: https://192.168.1.10:8080
Format: clusterconfig 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]| Flag | Description |
|---|---|
--peer-id | Peer ID (required) |
--peer-url | Agent URL (required) |
--cluster | Cluster ID or name (defaults to current cluster) |
--name | Human-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 Productionconfig delete-peer
Remove a peer entry from a cluster in your local config.
arctic config delete-peer <peer-id-or-name> [flags]| Flag | Description |
|---|---|
--cluster | Cluster ID or name (defaults to current cluster) |
arctic config delete-peer agent-c
arctic config delete-peer agent-c --cluster ProductionRequires 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
| Subcommand | Description |
|---|---|
list | List all credentials |
create | Create a new credential |
revoke | Revoke a credential |
rotate | Rotate the shared cluster credential secret |
credentials list
List credentials for the current license. Required scope: urn:tillered:arctic:credentials.read.
arctic credentials listEach 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>| Flag | Description |
|---|---|
--scopes | Comma-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.readIMPORTANT: 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.readcredentials 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]| Flag | Description |
|---|---|
--update-config | Update 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
| Subcommand | Description |
|---|---|
snapshot | Take 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 snapshotThe 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
| Flag | Description |
|---|---|
--readyz | Check 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 outputDefault output:
Peer ID: peer_01KBYMHC35F0N070F3XXMEZAZR
Name: agent-1
URL: https://192.168.3.251:8080
Status: ok
Timestamp: 2025-12-09 04:43:41 UTCJSON 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" ;;
esaclicense
Manage the agent license. The agent re-verifies the license on every startup.
Synopsis
arctic license <subcommand> [flags]Subcommands
| Subcommand | Description |
|---|---|
status | Show license usage status |
update | Update the license |
license status
Show license usage status. Required scope: urn:tillered:arctic:admin.
arctic license statusLicense 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: Ed25519Use -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>| Flag | Description |
|---|---|
--license-file | Path to the new license file (required) |
arctic license update --license-file new-license.jsonLicense updated
Customer ID: cust_xyz789...
License ID: lic_def456...
Expires: 2026-12-31 23:59:59 UTC
Max Nodes: 20
Max Services: 200Notes:
- 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
| Subcommand | Description |
|---|---|
list | List all peers |
get | Get details of a specific peer |
add | Add a remote peer |
update | Update peer metadata |
delete | Delete a peer |
replace | Replace a dead member with the rebuilt agent at its endpoint |
remove-self | Remove the local peer from the cluster |
peers list
List peers in the cluster. Required scope: urn:tillered:arctic:peers.read.
arctic peers list [flags]| Flag | Description |
|---|---|
--local | Show only the local peer (mutually exclusive with --remote) |
--remote | Show only remote peers (mutually exclusive with --local) |
arctic peers list
arctic peers list --remote
arctic peers list -jID NAME ADDRESS
peer_01KBYMHC35F0N070F3XXMEZAZR agent-1 192.168.3.251:8080
peer_01KBYMD32Z2RVDE3G5Y9M530QR agent-2 10.10.40.95:8080peers 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 UTCpeers add
Add a remote peer to the cluster. Required scope: urn:tillered:arctic:peers.write.
arctic peers add <peer-address> [flags]| Flag | Description |
|---|---|
--name | Human-readable name for the peer |
arctic peers add 192.168.1.20:8080
arctic peers add 192.168.1.20:8080 --name prod-svrThis 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]| Flag | Default | Description |
|---|---|---|
--name | Human-readable name for the peer | |
--qos | true | Enable QoS for this peer (use --qos=false to disable) |
arctic peers update peer_01HXYZ... --name prod-svr-2
arctic peers update peer_01HXYZ... --qos=falsepeers 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... --yesTo 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]| Flag | Description |
|---|---|
--dry-run | Show 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 --yesThe 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
| Subcommand | Description |
|---|---|
list | List routes for a service |
add | Add a route to a service |
update | Update a route |
delete | Delete 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/24routes add
Add a route to a service. Required scope: urn:tillered:arctic:services.write.
arctic routes add --service <service-id> [flags]| Flag | Description |
|---|---|
--service | Service ID (required) |
--priority | Route priority (lower value = higher priority) |
--source-cidr | Source CIDR (e.g. 10.0.0.0/8) |
--dest-cidr | Destination 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/24routes update
Update a route. Required scope: urn:tillered:arctic:services.write.
arctic routes update --service <service-id> --route <route-id> [flags]| Flag | Description |
|---|---|
--service | Service ID (required) |
--route | Route ID (required) |
--priority | New priority |
--source-cidr | New source CIDR |
--dest-cidr | New destination CIDR |
arctic routes update --service svc_01HSVCABC123... --route 6 --priority 200routes 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 --yesRoute 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.
| Priority | Source CIDR | Dest CIDR | Order |
|---|---|---|---|
| 200 | 10.1.0.0/16 | 192.168.100.0/24 | 1st (source + dest) |
| 100 | 10.0.0.0/8 | (none) | 2nd (source only) |
| 100 | (none) | 192.168.0.0/16 | 3rd (dest only) |
services
Manage services for network routing. Alias: service.
Synopsis
arctic services <subcommand> [flags]Subcommands
| Subcommand | Description |
|---|---|
list | List services |
get | Get details of a specific service |
create | Create a new service |
update | Update service settings |
delete | Delete a service |
services list
List services. Required scope: urn:tillered:arctic:services.read.
arctic services list [flags]| Flag | Description |
|---|---|
--source-peer | Filter by source peer ID |
--target-peer | Filter by target peer ID |
--requires-interface | Filter to services requiring a MACVLAN interface |
arctic services list
arctic services list --target-peer peer_01HXYZ...
arctic services list -jTable 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, -=noneUse -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: sourceIf 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]| Flag | Default | Description |
|---|---|---|
--target-peer | Target peer ID (required) | |
--source-peer | local peer | Source peer ID |
--transport | tcp | Transport type: tcp or kcp |
--requires-interface | false | Create a MACVLAN interface |
--desired-ipv4 | IPv4 CIDR for the interface | |
--interface-parent-dev | Parent device for the MACVLAN interface (empty = auto-detect) | |
--uplink-dev | Egress device for this service's traffic (empty = auto-detect) | |
--fully-transparent | false | Enable fully transparent mode |
--bandwidth-limit | 0 | Bandwidth limit in Mbps (0 = unlimited) |
--mptcp | false | Enable MPTCP for tunnel dials (tcp transport only; rejected with kcp) |
--mptcp-subflows | 0 | Requested 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 \
--mptcpservices update
Update a service's settings. Required scope: urn:tillered:arctic:services.write.
arctic services update <service-id> [flags]| Flag | Description |
|---|---|
--requires-interface[=BOOL] | Set the MACVLAN interface requirement |
--fully-transparent[=BOOL] | Set fully transparent mode |
--bandwidth-limit | Bandwidth limit in Mbps (0 = unlimited) |
--desired-ipv4 | IPv4 CIDR for the interface |
--uplink-dev | Set the egress device for this service (pass an empty value to clear and return to auto-detect) |
--interface-parent-dev | Set the MACVLAN parent device (an empty value clears) |
--mptcp[=BOOL] | Set MPTCP for tunnel dials (--mptcp=false disables) |
--mptcp-subflows | Requested 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
| Subcommand | Description |
|---|---|
refresh | Fetch peers and services from the cluster and update the cache |
status | Show cache location, age, and contents |
delete | Delete the cache file |
cache refresh
arctic cache refreshCache updated: 2 success, 0 failed (84ms)cache status
arctic cache statusCache Location: ~/.config/arctic/cache.json
Last Updated: 2025-12-09 04:43:41 UTC (12m ago)
TTL: 1h
Cluster: clu_01HABC...
Peers: 3
Services: 5If 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
| Subcommand | Description |
|---|---|
backups | List rollback snapshots captured by compose apply |
unlock | Release 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]| Flag | Description |
|---|---|
--state-dir | Override 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-44ZROLLBACK 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]| Flag | Description |
|---|---|
--state-dir | Override the .arctic/ state directory location |
--force | Admin override on the cluster tier; bypasses the holder-id check when releasing the cluster lock |
--cluster-only | Skip the local file lock; only release the cluster lock |
--local-only | Skip 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
| Flag | Default | Description |
|---|---|---|
--allow-write | false | Enable 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
| Flag | Description |
|---|---|
--context, -c | Context to use (peer ID, peer name, cluster ID, or cluster name) |
--url | Override the agent URL for this command |
--token | Use a specific bearer token instead of configured credentials |
--client-id | Override the OAuth client ID (e.g. cli_01HXYZ...) |
--client-secret | Override the OAuth client secret (e.g. sec_...) |
--insecure | Connect over TLS without verifying the pinned agent identity, and accept and re-learn a changed identity |
--known-hosts | Path to the trust-on-first-use known-hosts file (default: known_hosts.yaml in the user config dir) |
--project-file | Compose file for project-scope target resolution (default: cluster.yaml in the current directory; the literal none disables it) |
--recovery-token | Recovery token value (sent as the X-Arctic-Recovery header) |
--recovery-token-file | Path 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:
- Explicit
--urlor--contextflags (or their environment variables) - The project directory:
cluster.yamlin the current directory (override the file with--project-file/ARCTIC_PROJECT_FILE; the literalnonedisables project resolution) - 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
| Flag | Description |
|---|---|
--output, -o | Output format: table, json, or yaml |
--json, -j | Shorthand for -o json |
--no-headers | Omit column headers in table output |
--quiet, -q | Suppress 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
| Flag | Description |
|---|---|
--yes, -y | Skip confirmation prompts for destructive operations |
--debug, --verbose, -v | Enable debug logging |
--trace | Show full HTTP request and response traces |
--timeout | Request timeout (e.g. 30s, 1m30s) |
--config | Path to the config file (default ~/.config/arctic/config.yaml) |
Environment variables
| Flag | Environment variable |
|---|---|
--config | ARCTIC_CONFIG |
--context | ARCTIC_CONTEXT |
--url | ARCTIC_URL |
--token | ARCTIC_TOKEN |
--client-id | ARCTIC_CLIENT_ID |
--client-secret | ARCTIC_CLIENT_SECRET |
--known-hosts | ARCTIC_KNOWN_HOSTS |
--project-file | ARCTIC_PROJECT_FILE |
--recovery-token | ARCTIC_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
| Code | Name | Description |
|---|---|---|
| 0 | Success | Command completed successfully |
| 1 | General error | Unspecified error |
| 2 | Usage error | Invalid arguments, unknown flags, or a missing required value |
| 3 | Config error | Missing or invalid configuration |
| 4 | Connection error | Agent could not be reached |
| 5 | Auth error | Invalid or expired credentials |
| 6 | Partial failure | Some operations succeeded, others failed |
| 7 | Lock contention | A compose apply lock is held by another process |
| 130 | Interrupted | The 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.yamltrust store. The CLI exits naming the file rather than downgrading connections to unpinned TLS;--insecureis the deliberate unpinned override and the recovery path for repairing the store. - A downgraded CLI reading a
.arctic/cluster.statefile 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