
An Incus OS cluster keeps its quorum on three voters, but nothing stops you grafting on further nodes that only host instances. Those database-client members add compute capacity without weighing on the database, and they can live on a remote site. This guide shows how to add a worker, target instance placement (a specific node, a group), reflect topology with failure domains, and administer a distant node. Intended audience: administrators of an Incus OS cluster who want to extend it beyond its three-node core.
What you will learn
- Why a worker joins the cluster as a database-client.
- Add a member outside the quorum and keep it a client.
- Place instances on a specific node or a group.
- Model physical topology with failure domains.
- Administer a remote node over the network, and its limits.
Prerequisites
- A healthy three-node Incus OS cluster. See roles, quorum and high availability for the database roles.
- The seed join procedure described in Incus OS without a shell.
- A trusted Incus client with a remote to one member (
node1:).
Why a worker joins as a database-client
A cluster assigns its database roles within two limits: 3 voters (cluster.max_voters) and 2 stand-by members (cluster.max_standby). Once those five slots are taken, any additional member becomes a database-client: it neither replicates the database nor votes, it just hosts instances. That is exactly what a worker should be: capacity, with no responsibility for consensus.
To be certain a new node stays a client and does not pick up a database role, set stand-by slots to zero before adding it:
incus config set node1: cluster.max_standby 0After joining, incus cluster list confirms the result: the worker appears with no role, the roles column empty.
node1,databasenode2,"database-leader,database"node3,databasenode4,Adding the worker to the cluster
On Incus OS, a node joins the cluster through a seed, never through a shell. You issue a join token from an existing member, then provision the new node with a seed carrying that token, the cluster certificate and the configuration of its local pool:
incus cluster add node1:node4The rest of the mechanics (image, incus.json seed with the cluster block, certificate, pool member_config) is identical to joining a core node and is covered in Incus OS without a shell. The only difference for a worker is the max_standby setting applied before the join. Once first boot is done, the node updates itself to the cluster version and shows as ONLINE, ready to receive instances.
Placing instances on a worker
By default, Incus picks the host node by load. To force a placement, the --target option names a specific member:
incus launch images:debian/12 node1:app1 --target node4 -s local -n incusbr0The instance starts on the worker, as the location column confirms:
| app1 | RUNNING | node4 |That direct targeting suits pinning a workload to a given node, for instance a worker with particular hardware or sitting on a specific site.
Targeting a subset with cluster groups
Pinning node by node quickly becomes tedious. Cluster groups gather members under a logical name you can launch instances towards without knowing the exact node. You create a group, then assign members to it:
incus cluster group create node1:workersincus cluster group assign node1:node4 default,workersA member always belongs to the default group and can accumulate others. To launch an instance on the group, prefix the group name with @ in --target:
incus launch images:debian/12 node1:calc1 --target @workers -s local -n incusbr0Incus then places the instance on a member of the workers group. That is the ideal mechanism for reserving a family of workloads to a category of nodes: the workers, the ones with a GPU, or the ones on a given site.
Reflecting topology with failure domains
A failure domain tells Incus which members share a common risk (same rack, same site, same power feed). When a database member fails, Incus tries to reassign its role to another member of the same domain, which avoids concentrating every voter in one physical place. The domain is set by editing the member definition:
incus cluster show node1:node4 | sed "s/^failure_domain:.*/failure_domain: siteB/" | incus cluster edit node1:node4Reading the member back confirms the change:
failure_domain: siteBBy assigning domains that match physical reality, you help the cluster spread roles and instances sensibly across locations instead of stacking them onto a single failure zone.
The remote node case
A worker can sit on another site. Two points deserve attention. First, connectivity: on first boot, the node applies its join seed and contacts the leader at the address it was given. At that moment no application service is up yet, so that address has to be reachable at the infrastructure level (routing, site VPN). The built-in Tailscale service only activates after boot: it serves to administer the node once it is in place, not to carry the initial join.
Once the worker is in service, enabling it on the tailnet makes it reachable through a stable private 100.x address, handy for driving its API remotely without exposing a public port. The procedure is described in connecting nodes to Tailscale.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
The new node takes a database role | max_standby non-zero at join time | Set cluster.max_standby 0 before adding the worker |
--target @group fails | Group missing or empty | Create the group and assign at least one member |
The remote worker goes OFFLINE intermittently | Latency above offline_threshold | Raise cluster.offline_threshold, check the link |
| An instance cannot migrate off the worker | Volume on local storage | Put the data on shared storage |
Key points
- Beyond the 5 database slots (3 voters plus 2 stand-by), a member becomes a database-client.
- Setting
cluster.max_standbyto 0 guarantees a new node stays a pure worker. - On Incus OS you always join through a seed; only the
max_standbysetting distinguishes a worker. --target <node>pins an instance;--target @<group>aims at a cluster group.- Failure domains reflect physical topology and guide how roles are spread.
- A remote node needs infrastructure connectivity to join; Tailscale serves administration, not the join itself.