Skip to content

Hosts

The hosts argument says which hosts a circuit configures. It is required.

circuit "web" {
hosts = {
web1 = "203.0.113.10"
web2 = "203.0.113.11"
}
ssh {
user = "deploy"
}
}

Conn3x uses hosts as the for_each of every resource the circuit creates, so it follows OpenTofu’s rules for for_each: it must be a map or a set of strings, and its keys must be known when Conn3x plans. Its values can be unknown until apply, such as the address of a server created in the same plan.

Each key is the host’s name, and each value is the address Conn3x connects to: a hostname or an IP address.

circuit "web" {
hosts = {
web1 = "203.0.113.10"
web2 = "web2.example.com"
}
ssh {
user = "deploy"
}
}

A value can be an object with an address and any of the connection settings listed in Host object fields. These override the circuit’s ssh block for that host. A map can mix addresses and objects.

circuit "fleet" {
hosts = {
web1 = "10.0.0.4"
db1 = { address = "10.0.0.5", user = "admin", port = 2222 }
}
ssh {
user = "deploy"
}
}

A set of strings uses each address as the host’s name too. Wrap a list in toset(): a list or tuple is an error.

circuit "web" {
hosts = toset(["203.0.113.10", "203.0.113.11"])
ssh {
user = "deploy"
}
}

Any expression that produces one of these forms works: a variable, a local, a for expression over a resource, or a merge() of several maps. An inventory is an ordinary variable, and a group is a map you merge with another.

variable "web_hosts" {
type = map(string)
}
variable "db_hosts" {
type = map(string)
}
circuit "monitoring" {
hosts = merge(var.web_hosts, var.db_hosts)
ssh {
user = "deploy"
}
}

To configure servers created in the same plan, build the map from the resource. The circuit’s steps then wait for the servers:

variable "app_addresses" {
type = map(string)
}
resource "terraform_data" "app" {
for_each = var.app_addresses
input = each.value
}
circuit "app" {
hosts = { for name, server in terraform_data.app : name => server.output }
ssh {
user = "root"
}
}

For a complete example with DigitalOcean droplets and host keys pinned at creation, see Provision and configure.

Field Type Description
address string Required. The hostname or IP address to connect to.
user string Overrides the ssh block’s user.
port number Overrides the ssh block’s port.
private_key_file string Overrides the ssh block’s private_key_file.
host_key string Overrides the ssh block’s host_key. Use it to pin each host’s own key.
jump_hosts list of strings Overrides the ssh block’s jump_hosts. An empty list connects to this host directly.

Only these five settings can be overridden per host. The others, such as become, known_hosts_file and timeout, come from the ssh block for every host.

A host object can hold fields of your own as well. Conn3x doesn’t use them to connect, and steps can read them as each.value.FIELD:

circuit "app" {
hosts = {
app1 = { address = "10.0.1.5", role = "primary" }
app2 = { address = "10.0.1.6", role = "replica" }
}
ssh {
user = "deploy"
}
step "role" {
file {
path = "/etc/app/role"
content = "${each.value.role}\n"
}
}
}

What a host object can’t hold is an ssh setting that only the ssh block takes: ssh settings such as become, timeout and known_hosts_file belong in the ssh block. Naming one in a host object is an error, not a setting that quietly does nothing. validate reports it when hosts is known, and plan reports it otherwise:

Error: Unsupported field in host object
Host "web1" sets "become", which only the circuit's ssh block takes. A host
object can set address, user, port, private_key_file, host_key and
jump_hosts, plus any fields of your own to read with each.value.

See The ssh block for how host objects, the ssh block and an SSH config file combine.

Inside a step’s module block, each.key is the host’s name and each.value is its entry in hosts: the address string, or the host object. With a set of addresses, both are the address.

The same holds in a step with its own for_each. There, item.key and item.value are the item, and each is still the host. See Steps.

Every resource a circuit creates has one instance per host, keyed by the host’s key in hosts:

hosts Instance keys
{ web1 = "203.0.113.10" } ["web1"]
{ db1 = { address = "10.0.0.5" } } ["db1"]
toset(["203.0.113.10"]) ["203.0.113.10"]

A step with for_each has one instance per host and item, keyed "HOST/ITEM", such as ["web1/git"].

The key is the host’s identity in state, so choose names that won’t change.

  • Add a host to hosts, and the next plan creates every step on it, and runs every notified handler on it.
  • Remove a host, and the next plan destroys that host’s instances of every step. What happens on the host depends on each step’s on_remove.
  • Rename a host’s key, and Conn3x sees one host removed and another added. To keep the existing instances under the new name, use a moved block or conn3x state mv. See Resource addresses.