Quickstart
Create a cluster using compose apply
By the end of this quickstart you will have two hosts accelerating TCP between them. You define a small cluster (two peers and one service) in a YAML compose file, apply it with a single command, and route your traffic through it.
Set Up a Project Directory
cluster.yaml is an operator-side file. It stays with you, on your workstation or any machine that can reach the peers; it does not live on the agent hosts, does not go in /opt/tillered, and needs no root. A small git-managed project directory works well:
mkdir my-cluster && cd my-cluster
git init
cp /path/to/license.json .Commit cluster.yaml (created below) and license.json. The first apply also creates a .arctic/ directory next to the file: a generated local state cache. It holds no secrets and is safe to delete, and apply writes a .gitignore inside it so it cannot be committed by accident; leave it out of version control.
Working from this directory has a second benefit: the CLI resolves its target cluster from the cluster.yaml in the current directory, so commands run here need no extra setup. More on that in Verify the Setup.
Create a Cluster Configuration
Configuration Builder
Prefer a visual tool? Use the Configuration Builder to build and validate your cluster.yaml through a web interface. It runs the same validation rules as the CLI.
Create a file called cluster.yaml with your cluster configuration:
version: v1
license: license.json # Path to your license file
peers: # Define all hosts in the cluster
- name: node-a
endpoints:
- https://192.168.1.10:8080
- name: node-b
endpoints:
- https://192.168.1.20:8080
services:
- name: tunnel-a-to-b
source_peer: node-a # Where traffic enters
target_peer: node-b # Where traffic exits
transport_type: tcp
interface:
enabled: true # Create a MACVLAN interface for routing
ipv4: 192.168.1.50/24 # Static address on the LAN, used as the route targetEach peer can list more than one entry under endpoints (for example a LAN address and a public address); the cluster tries them in order. Write endpoints with an explicit https:// scheme: it makes the intent unambiguous, and v1.4.2 CLI builds from before the 2026-07-22 patch fail the very first bootstrap with a connection-reset error without it (see TLS and trust). The full schema is in the compose reference.
Pick an unused address on the source peer's LAN for interface.ipv4; clients reach the tunnel by routing through it. Omitting ipv4 requests a DHCP lease instead, but many networks do not answer DHCP for a MAC they have never seen, and the interface then sits without an address and routes nothing - a static address is the predictable choice for a first cluster.
Place your license.json file in the same directory as cluster.yaml.
Apply the Configuration
Apply the configuration to create your cluster:
arctic compose apply ./cluster.yamlApply shows the full plan and prompts before it touches anything. On a fresh cluster the plan covers everything: the bootstrap itself, each peer join, and each service create. Nothing mutates until you confirm. Pass --yes to skip the prompt in scripts, or --dry-run to print the plan and exit without changing anything, which is safe even on a fresh cluster. Along the way, apply learns and pins each agent's TLS identity on first contact; see TLS and trust.
Running pre-flight connectivity checks...
[ok] node-a
[ok] node-b
Discovering peers...
[new] node-a (192.168.1.10:8080)
[new] node-b (192.168.1.20:8080)
Planned changes:
+ peer: bootstrap "node-a" (192.168.1.10:8080)
+ peer: join "node-b" (192.168.1.20:8080)
+ service: create "tunnel-a-to-b" (node-a -> node-b)
Apply these changes? [y/N]:In a script or CI pipeline there is no terminal to prompt on; a non-TTY run
without --yes prints the plan and then exits with code 2 rather than
mutating anything.
Bootstrap Credentials
On success, the tail of a first apply looks like this:
Cluster bootstrapped successfully!
Save these credentials (cannot be retrieved later):
export ARCTIC_CLIENT_ID=cli_01HXYZABC123...
export ARCTIC_CLIENT_SECRET=sec_abc123...
Break-glass credentials written to ~/.config/arctic/break-glass.clu_01HABC....json (0600).
Store the pair somewhere safe (offline) and delete the file once secured;
pass --credentials-file none to keep it off disk entirely.
...
Configuration applied successfully.
Working credential minted for CLI use (scoped, no admin).
Cluster saved to config (working credential) and set as currentA first apply mints two credentials for the new cluster:
- A scoped working credential, saved to
~/.config/arctic/config.yaml. All day-to-day commands (peers list,services get, latercompose applyruns) use it automatically; you do not need to export anything. - The break-glass admin pair, printed once and also written to
break-glass.<cluster-id>.json(mode 0600) next to the CLI config. You need it again only for admin-only operations such as credential management, supplied per invocation viaARCTIC_CLIENT_ID/ARCTIC_CLIENT_SECRETor the--client-id/--client-secretflags.
Move the break-glass pair somewhere offline (a password manager or vault) and delete the file once it is secured. --credentials-file picks a different destination for it; the literal value none restores print-only behaviour.
Changed in v1.4.2
Earlier releases saved a single admin credential to the CLI config and printed it exactly once. As of v1.4.2 the config holds a least-privilege working credential instead, and the admin pair is break-glass only, written to break-glass.<cluster-id>.json so scripted runs cannot lose it. If you lose both credentials, see Access recovery.
Verify the Setup
After compose apply returns, confirm the cluster came up as expected. Run these from the project directory: the CLI resolves the target cluster from cluster.yaml and uses the saved working credential, so no --url flag or context setup is needed.
# List peers and their connectivity
arctic peers list
# List services
arctic services list
# Get full service details (including the assigned interface IP)
arctic services get <service-id>The <service-id> is a ULID shown in the output of arctic services list. peers list and services list give you a summary table; services get returns the full detail for one service.
Updating Your Cluster
cluster.yaml is the source of truth. To change the cluster, edit the file and re-run the same apply flow. Validate first, preview the diff, then apply:
# Check the configuration is well-formed
arctic compose validate ./cluster.yaml
# Preview what would change against the live cluster
arctic compose diff ./cluster.yaml
# Apply the changes
arctic compose apply ./cluster.yamlThe CLI compares your file against the live cluster and updates only what changed, so re-applying an unchanged file is a no-op.
Service Configuration Options
The quickstart example uses the default MACVLAN interface mode. Here are additional configuration options.
Static IP Assignment
Assign a static IPv4 address to the service interface:
services:
- name: tunnel-a-to-b
source_peer: node-a
target_peer: node-b
transport_type: tcp
interface:
enabled: true
ipv4: 192.168.3.32/24 # Static IPv4 with subnetPolicy-Based Routing
Route specific subnets through the tunnel instead of using a MACVLAN interface. Use this when MACVLAN is unavailable (for example, some cloud environments):
services:
- name: tunnel-a-to-b
source_peer: node-a
target_peer: node-b
transport_type: tcp
routes:
- dest_cidr: 172.31.8.0/24
priority: 100
- dest_cidr: 172.31.0.0/16
priority: 200Priority breaks ties between routes of equal specificity, and a lower priority value wins. In the example above, traffic to 172.31.8.0/24 matches the first route (priority 100) before the broader 172.31.0.0/16 route (priority 200). Specificity is checked before priority: a MACVLAN interface match beats a source+dest CIDR match, which beats a source-only CIDR, which beats a dest-only CIDR.
Two-Way Communication
A service carries traffic one way, from its source peer to its target peer. To accelerate traffic in both directions between the same two hosts, define a second service pointing the other way. Each direction is its own service with its own interface:
version: v1
license: license.json
peers:
- name: node-a
endpoints:
- https://192.168.1.10:8080
- name: node-b
endpoints:
- https://192.168.1.20:8080
services:
- name: tunnel-a-to-b
source_peer: node-a
target_peer: node-b
transport_type: tcp
interface:
enabled: true
- name: tunnel-b-to-a
source_peer: node-b
target_peer: node-a
transport_type: tcp
interface:
enabled: trueTraffic entering node-a exits at node-b through tunnel-a-to-b, and traffic entering node-b exits at node-a through tunnel-b-to-a. The Configuration Builder can build and validate multi-service configurations like this, with the same rules the CLI applies.
Troubleshooting
Agent Not Starting
Check logs for errors:
journalctl -u arctic -fCommon causes: port 8080 already in use, or the agent not running as root.
Compose Apply Fails
- Verify all agents are running:
curl -sk https://<ip>:8080/livez(the-kis expected; the agent's certificate is self-signed, see Installation) - Check the license file exists and is valid (the agent re-verifies the license on every startup)
- Ensure network connectivity between agents
Handshake Fails
- Both agents must use the same license
- Agents must reach each other on port 8080
- Check for firewall rules blocking ports 8080/TCP, 51840/UDP, and 61000/TCP
More failure modes, including TLS and credential errors, are covered in Troubleshooting.
Next Steps
You now have a working Arctic cluster with encrypted tunnels between your hosts. Explore the Compose guide for more advanced cluster management options.