From 69d5cca032a6589506fe455f5e957045faa20f0b Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 6 May 2026 16:55:31 +0200 Subject: [PATCH 1/5] swap default Hetzner sizes from cax (ARM) to cpx (AMD) ARM cax* capacity at Hetzner has been chronically tight across all EU locations, causing repeated `resource_unavailable` placement failures on apply. Switch the supernode/fullnode defaults to the spec-equivalent AMD shared-vCPU sizes which have far better availability: - supernode: cax41 (16/32) -> cpx51 (16/32) - fullnode: cax31 (8/16) -> cpx41 (8/16) The arch label logic already keys off the `^cax` regex, so labels will automatically flip to `arch:amd64`. --- terraform/devnet-0/hetzner.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/devnet-0/hetzner.tf b/terraform/devnet-0/hetzner.tf index 454888c..c09d70a 100644 --- a/terraform/devnet-0/hetzner.tf +++ b/terraform/devnet-0/hetzner.tf @@ -8,12 +8,12 @@ variable "hcloud_ssh_key_fingerprint" { variable "hetzner_supernode_size" { type = string - default = "cax41" + default = "cpx51" } variable "hetzner_fullnode_size" { type = string - default = "cax31" + default = "cpx41" } variable "hetzner_regions" { From 32db5482d444d8a335aafe60b198be72e86b7089 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 6 May 2026 17:06:13 +0200 Subject: [PATCH 2/5] swap to cx (new gen) instead of cpx (legacy) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hetzner is rolling out a new CX line that replaces the CPX series in many locations. CPX SKUs (cpx41/cpx51) are no longer creatable in fsn1/nbg1/hel1, returning `unsupported location for server type (invalid_input)` from the API. Switch defaults to the new CX equivalents at identical specs and slightly lower price: - supernode: cpx51 (16/32) -> cx53 (16/32) — €31.99 -> €22.99 - fullnode: cpx41 (8/16) -> cx43 (8/16) — €24.49 -> €12.49 --- terraform/devnet-0/hetzner.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/devnet-0/hetzner.tf b/terraform/devnet-0/hetzner.tf index c09d70a..de663f7 100644 --- a/terraform/devnet-0/hetzner.tf +++ b/terraform/devnet-0/hetzner.tf @@ -8,12 +8,12 @@ variable "hcloud_ssh_key_fingerprint" { variable "hetzner_supernode_size" { type = string - default = "cpx51" + default = "cx53" } variable "hetzner_fullnode_size" { type = string - default = "cpx41" + default = "cx43" } variable "hetzner_regions" { From 37b8904e33e90d50ab45e589214fbe70df828a18 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 6 May 2026 17:24:33 +0200 Subject: [PATCH 3/5] add capacity-aware placement, swap defaults to cpx (new gen) Hetzner ARM (cax*) capacity has been chronically tight in EU and the legacy CPX line (cpx41/cpx51) is no longer creatable in some regions. Newer cpx42/cpx62 are widely available and still ~3-4x cheaper than DigitalOcean for equivalent specs. Two behavior changes layered together: 1. Default sizes flipped to the new-gen CPX: - supernode: cpx62 (16 vCPU / 32 GB / 640 GB) - fullnode: cpx42 (8 vCPU / 16 GB / 320 GB) 2. Capacity-aware placement: query `hcloud_datacenters` and `hcloud_server_type` data sources at plan time, build the set of locations whose datacenters currently report both SKUs as `available`, and round-robin only across those. Falls back to the full region list if every region is sold out. - `lifecycle { ignore_changes = [location] }` keeps existing servers pinned where they were placed even if next plan's filter would prefer a different region (location is replacement-forced otherwise). - `hcloud_server_network` reads the actual server location post-state so the network reference stays correct after any drift between planned and real location. Arch labels still flip to `arch:amd64` automatically via the existing `^cax` regex. --- terraform/devnet-0/hetzner.tf | 45 +++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/terraform/devnet-0/hetzner.tf b/terraform/devnet-0/hetzner.tf index de663f7..2d6fb1e 100644 --- a/terraform/devnet-0/hetzner.tf +++ b/terraform/devnet-0/hetzner.tf @@ -8,12 +8,12 @@ variable "hcloud_ssh_key_fingerprint" { variable "hetzner_supernode_size" { type = string - default = "cx53" + default = "cpx62" } variable "hetzner_fullnode_size" { type = string - default = "cx43" + default = "cpx42" } variable "hetzner_regions" { @@ -24,12 +24,45 @@ variable "hetzner_regions" { ] } +//////////////////////////////////////////////////////////////////////////////////////// +// DATA SOURCES +//////////////////////////////////////////////////////////////////////////////////////// +data "hcloud_server_type" "supernode" { + count = local.hetzner_has_servers ? 1 : 0 + name = var.hetzner_supernode_size +} + +data "hcloud_server_type" "fullnode" { + count = local.hetzner_has_servers ? 1 : 0 + name = var.hetzner_fullnode_size +} + +data "hcloud_datacenters" "all" { + count = local.hetzner_has_servers ? 1 : 0 +} + //////////////////////////////////////////////////////////////////////////////////////// // LOCALS //////////////////////////////////////////////////////////////////////////////////////// locals { hetzner_has_servers = length(local.hetzner_nodes) > 0 + required_hetzner_server_type_ids = local.hetzner_has_servers ? toset([ + tostring(data.hcloud_server_type.supernode[0].id), + tostring(data.hcloud_server_type.fullnode[0].id), + ]) : toset([]) + + hetzner_available_locations = local.hetzner_has_servers ? distinct([ + for dc in data.hcloud_datacenters.all[0].datacenters : dc.location.name + if alltrue([for tid in local.required_hetzner_server_type_ids : contains([for id in dc.available_server_type_ids : tostring(id)], tid)]) + ]) : [] + + hetzner_regions_filtered = [ + for r in var.hetzner_regions : r if contains(local.hetzner_available_locations, r) + ] + + hetzner_regions_effective = length(local.hetzner_regions_filtered) > 0 ? local.hetzner_regions_filtered : var.hetzner_regions + hetzner_network = { for region in var.hetzner_regions : region => { name = "${var.ethereum_network}-${region}" @@ -76,7 +109,7 @@ locals { ) ? var.hetzner_supernode_size : var.hetzner_fullnode_size ) - location = node.location != null ? node.location : var.hetzner_regions[i % length(var.hetzner_regions)] + location = node.location != null ? node.location : local.hetzner_regions_effective[i % length(local.hetzner_regions_effective)] ipv4_enabled = node.ipv4_enabled ipv6_enabled = node.ipv6_enabled } @@ -166,6 +199,10 @@ resource "hcloud_server" "main" { ipv4_enabled = each.value.ipv4_enabled ipv6_enabled = each.value.ipv6_enabled } + + lifecycle { + ignore_changes = [location] + } } resource "hcloud_server_network" "main" { @@ -173,5 +210,5 @@ resource "hcloud_server_network" "main" { for vm in local.hcloud_vms : vm.id => vm } server_id = hcloud_server.main[each.key].id - network_id = hcloud_network.main[each.value.location].id + network_id = hcloud_network.main[hcloud_server.main[each.key].location].id } From 942232a9341f33ce607d0e34c63d3ab25e25a8b8 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 6 May 2026 17:29:35 +0200 Subject: [PATCH 4/5] note SKU rationale in comment Capture why we landed on cpx42/cpx62 (capacity vs cax/legacy cpx, cost vs DigitalOcean) so future operators don't have to relitigate the choice. --- terraform/devnet-0/hetzner.tf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/terraform/devnet-0/hetzner.tf b/terraform/devnet-0/hetzner.tf index 2d6fb1e..2550f83 100644 --- a/terraform/devnet-0/hetzner.tf +++ b/terraform/devnet-0/hetzner.tf @@ -6,6 +6,10 @@ variable "hcloud_ssh_key_fingerprint" { default = "d6:76:2d:9c:5b:33:80:ff:0f:09:a2:10:9b:58:7e:dc" } +# cpx* (AMD shared) defaults: cax* (ARM) capacity is unreliable and the +# legacy cpx41/cpx51 SKUs are no longer creatable in some EU locations. +# cpx42/cpx62 are widely stocked and still ~3-4x cheaper than DigitalOcean +# for equivalent specs (e.g. cpx62 16/32/640 ~€50/mo vs DO ~€177/mo). variable "hetzner_supernode_size" { type = string default = "cpx62" From 29f32167405d37ce753572a83070edb2ed34e7d3 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Wed, 6 May 2026 17:30:35 +0200 Subject: [PATCH 5/5] revert: drop SKU rationale comment, moved to PR description --- terraform/devnet-0/hetzner.tf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/terraform/devnet-0/hetzner.tf b/terraform/devnet-0/hetzner.tf index 2550f83..2d6fb1e 100644 --- a/terraform/devnet-0/hetzner.tf +++ b/terraform/devnet-0/hetzner.tf @@ -6,10 +6,6 @@ variable "hcloud_ssh_key_fingerprint" { default = "d6:76:2d:9c:5b:33:80:ff:0f:09:a2:10:9b:58:7e:dc" } -# cpx* (AMD shared) defaults: cax* (ARM) capacity is unreliable and the -# legacy cpx41/cpx51 SKUs are no longer creatable in some EU locations. -# cpx42/cpx62 are widely stocked and still ~3-4x cheaper than DigitalOcean -# for equivalent specs (e.g. cpx62 16/32/640 ~€50/mo vs DO ~€177/mo). variable "hetzner_supernode_size" { type = string default = "cpx62"