This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Reference

Configuration, outputs, templates, and project structure reference.

Use this section when you need exact variable names, output formats, templates, or operational reference material.

1 - Variables Reference

Every OpenTofu input variable with its type, default, and effect.

All configurable inputs for the OpenTofu infrastructure. Set these in terraform.tfvars.

Required Variables

VariableTypeDescription
hcloud_tokenstringHetzner Cloud API Token (sensitive)

Cluster Configuration

VariableTypeDefaultDescription
cluster_namestring"k8s-cluster"Name prefix for all resources
locationstring"fsn1"Hetzner Cloud location (fsn1, nbg1, hel1, ash, hil)

Network Configuration

VariableTypeDefaultDescription
network_ip_rangestring"10.0.0.0/8"IP range for the private network
subnet_ip_rangestring"10.0.0.0/24"IP range for the subnet
network_zonestring"eu-central"Network zone (eu-central, us-east, us-west)
enable_public_ipv6booltrueEnable public IPv6 for replica control nodes and worker nodes. The master always has public IPv6 (required for cloudflared). Setting to false air-gaps replicas and workers.

Server Configuration

VariableTypeDefaultDescription
server_imagestring"debian-13"Server image to use
master_control_node_typestring"cx23"Server type for the master control node (runs cloudflared)
control_node_typeslist(object({type, count}))[]Server types and counts for replica control nodes
worker_node_typeslist(object({type, count}))[]Server types and counts for worker nodes

The cost-optimized x86 line is cx23, cx33, cx43, and cx53; the Arm equivalents are cax11 through cax41. See Cost Estimate for specifications, current prices, and the constraints on the Arm line.

Node type examples

# Master-only (default)
master_control_node_type = "cx23"
control_node_types = []
worker_node_types  = []

# 3 control nodes + 2 workers
control_node_types = [
  { type = "cx23", count = 2 },
]
worker_node_types = [
  { type = "cx23", count = 2 },
]

# Mixed worker types
worker_node_types = [
  { type = "cx23", count = 2 },
  { type = "cx33", count = 1 },
]

Authentication

VariableTypeDefaultDescription
root_passwordstring"ChangeMe123!"Root password for emergency Web-Console access (sensitive)
admin_userstring"kubernetes-admin"Admin user name for SSH access
keyboard_layoutstring"de"Keyboard layout for cloud-init

SSH Keys

If left empty, new keys will be auto-generated and stored in the OpenTofu state. When using custom keys, manage private keys yourself.

VariableTypeDefaultDescription
control_node_public_keystring""Public SSH key for control nodes. Leave empty to auto-generate.
worker_node_public_keystring""Public SSH key for worker nodes. Leave empty to auto-generate.
admin_node_public_keystring""Public SSH key for admin node. Leave empty to auto-generate.
ssh_key_prefixstring""Prefix for SSH key filenames. Defaults to cluster_name if empty.

Cloudflare Configuration

VariableTypeDefaultDescription
cloudflare_tunnel_domainstring""Domain for Cloudflare Tunnel SSH access (e.g., console.example.org)
cloudflare_tunnel_tokenstring""Cloudflare Tunnel token for automatic setup on master node (sensitive). Leave empty for manual setup.

Admin Node

VariableTypeDefaultDescription
enable_admin_nodebooltrueEnable a temporary admin node with public IPv6 for initial SSH access. Disable after Cloudflare Tunnel is configured.
admin_node_typestring"cx23"Server type for admin node

NAT64/DNS64

VariableTypeDefaultDescription
enable_nat64booltrueEnable NAT64/DNS64 for IPv4 reachability on IPv6-only nodes
dns64_resolverslist(string)["2a01:4f8:c2c:123f::1", "2a01:4f9:c010:3f02::1", "2a00:1098:2b::1"]DNS64 resolver addresses (nat64.net: Nuremberg, Helsinki, Amsterdam)

Kubernetes

VariableTypeDefaultDescription
enable_k8s_prereqsbooltrueInstall Kubernetes prerequisites (container runtime, kubeadm, kubelet, kubectl) via cloud-init
kubernetes_versionstring"1.32"Kubernetes minor version for the pkgs.k8s.io apt source
container_runtimestring"containerd"Container runtime for Kubernetes nodes: "containerd" or "cri-o"

2 - Outputs Reference

Every OpenTofu output, what consumes it, and how to query it.

All outputs exposed by the OpenTofu configuration. These are consumed by the setup scripts and can be queried manually.

Network Information

OutputDescription
network_idID of the private network
network_nameName of the private network

Cluster Metadata

OutputDescription
cluster_nameName of the cluster
admin_userAdmin user name
ssh_key_prefixPrefix used for SSH key filenames

Master Control Node

OutputDescription
master_control_node_idID of the master control node
master_control_node_nameName of the master control node
master_control_node_private_ipPrivate IP of the master control node

All Control Nodes (master + replicas)

OutputDescription
control_node_countTotal number of control nodes (master + replicas)
control_node_idsIDs of all control nodes
control_node_namesNames of all control nodes
control_node_private_ipsPrivate IPs of all control nodes

Worker Nodes

OutputDescription
worker_node_countNumber of worker nodes
worker_node_idsIDs of worker nodes
worker_node_namesNames of worker nodes
worker_node_private_ipsPrivate IPs of worker nodes

Admin Node

OutputDescription
admin_node_idID of the admin node (null if disabled)
admin_node_nameName of the admin node (null if disabled)
admin_node_ipv6Public IPv6 address of the admin node (null if disabled)
admin_node_private_ipPrivate IP of the admin node (null if disabled)
enable_admin_nodeWhether the admin node is enabled

SSH Keys

These outputs are sensitive when auto-generated keys are used.

OutputSensitiveDescription
control_node_ssh_private_keyYesPrivate SSH key for control nodes (only if auto-generated)
control_node_ssh_public_keyNoPublic SSH key for control nodes
worker_node_ssh_private_keyYesPrivate SSH key for worker nodes (only if auto-generated)
worker_node_ssh_public_keyNoPublic SSH key for worker nodes
admin_node_ssh_private_keyYesPrivate SSH key for admin node (only if auto-generated)
admin_node_ssh_public_keyNoPublic SSH key for admin node
using_custom_keysNoMap showing which node roles use custom keys

SSH Config

OutputDescription
ssh_config_snippetSSH config snippet for ~/.ssh/config. Generates Host entries for admin-node (ProxyJump), cloudflare tunnel (ProxyCommand), and all nodes. All entries include IdentitiesOnly yes.

Feature Flags

OutputDescription
cloudflare_tunnel_domainConfigured Cloudflare Tunnel domain
cloudflare_tunnel_configuredWhether cloudflare_tunnel_token was set, i.e. whether cloud-init installed the tunnel automatically
nat64_enabledWhether NAT64/DNS64 is enabled
k8s_prereqs_enabledWhether Kubernetes prerequisites are installed via cloud-init

Next Steps Banner

OutputDescription
next_stepsInstructions banner displayed after tofu apply

Querying outputs

# List all outputs
tofu output

# Get a specific output
tofu output master_control_node_private_ip

# Get a sensitive output (raw)
tofu output -raw control_node_ssh_private_key

# Export SSH key to file
tofu output -raw control_node_ssh_private_key > ~/.ssh/k8s-cluster_control-node_key
chmod 600 ~/.ssh/k8s-cluster_control-node_key

3 - Cloud-Init Templates

The three cloud-init templates, their template variables, and their conditional sections.

Cloud-init templates are located in cloud-init/ and rendered by OpenTofu via templatefile() in main.tf. They configure each server on first boot.

admin-node.yaml.tpl

Purpose: Minimal jump host for initial SSH access.

Used by: hcloud_server.admin_node

Template variables:

VariableSource
ssh_public_keylocal.admin_node_public_key
root_passwordvar.root_password
admin_uservar.admin_user
keyboard_layoutvar.keyboard_layout

What it configures:

  • Admin user with sudo NOPASSWD
  • SSH hardening with AllowTcpForwarding yes (needed for ProxyJump)
  • fail2ban for SSH protection
  • UFW allowing public SSH (port 22 from anywhere)

control-node.yaml.tpl

Purpose: Kubernetes control plane node with optional Cloudflare Tunnel.

Used by: hcloud_server.master_control_node, hcloud_server.control_node_replica

Template variables:

VariableSource
ssh_public_keylocal.control_node_public_key
root_passwordvar.root_password
admin_uservar.admin_user
keyboard_layoutvar.keyboard_layout
is_mastertrue for master, false for replicas
enable_nat64var.enable_nat64
dns64_resolversvar.dns64_resolvers
enable_k8s_prereqsvar.enable_k8s_prereqs
kubernetes_versionvar.kubernetes_version
container_runtimevar.container_runtime
cloudflare_tunnel_tokenvar.cloudflare_tunnel_token (master), "" (replicas)

What it configures:

  • Admin user with sudo NOPASSWD and root password for emergency console access
  • SSH hardening (key-only auth, AllowTcpForwarding yes)
  • fail2ban for SSH protection
  • UFW: SSH from internal network (10.0.0.0/8) + localhost (for Cloudflare Tunnel)

Conditional sections:

  • is_master = true: Installs cloudflared, allows SSH from localhost, creates /etc/cloudflared/config.yml with edge-ip-version: "6". When cloudflare_tunnel_token is set, runs cloudflared service install <token> to auto-configure the tunnel as a systemd service.
  • is_master = false: Skips cloudflared installation (token always empty for replicas)
  • enable_nat64 = true: Configures DNS64 resolvers in systemd-resolved, adds NAT64 route (64:ff9b::/96), creates networkd-dispatcher persistence script
  • enable_k8s_prereqs = true: Installs the selected container runtime (containerd with SystemdCgroup and sandbox image fix, or cri-o from OBS repo with kubelet socket configuration), kubeadm, kubelet, kubectl, loads kernel modules (overlay, br_netfilter), sets sysctl params, disables swap, opens kubelet (10250) + etcd (2379-2380) ports

worker-node.yaml.tpl

Purpose: Kubernetes worker node with restricted network access.

Used by: hcloud_server.worker_node

Template variables:

VariableSource
ssh_public_keylocal.worker_node_public_key
root_passwordvar.root_password
admin_uservar.admin_user
keyboard_layoutvar.keyboard_layout
enable_nat64var.enable_nat64
dns64_resolversvar.dns64_resolvers
enable_k8s_prereqsvar.enable_k8s_prereqs
kubernetes_versionvar.kubernetes_version
container_runtimevar.container_runtime

What it configures:

  • Admin user with sudo NOPASSWD and root password for emergency console access
  • SSH hardening with AllowTcpForwarding no (workers cannot be used as jump hosts)
  • fail2ban for SSH protection
  • UFW: deny all by default, SSH from internal network only, outbound limited to DNS + HTTP/S

Conditional sections:

  • enable_nat64 = true: Uses DNS64 resolvers instead of Hetzner DNS, adds NAT64 prefix UFW rule
  • enable_k8s_prereqs = true: Same as control node prerequisites (runtime selected by container_runtime), but without etcd ports

Template rendering

Templates are rendered in main.tf via templatefile():

user_data = templatefile("${path.module}/cloud-init/control-node.yaml.tpl", {
  ssh_public_key         = local.control_node_public_key
  root_password          = var.root_password
  admin_user             = var.admin_user
  keyboard_layout        = var.keyboard_layout
  is_master              = true
  enable_nat64           = var.enable_nat64
  dns64_resolvers        = var.dns64_resolvers
  enable_k8s_prereqs     = var.enable_k8s_prereqs
  kubernetes_version     = var.kubernetes_version
  container_runtime      = var.container_runtime
  cloudflare_tunnel_token = var.cloudflare_tunnel_token
})

4 - Ansible Playbooks Reference

Purpose, variables, and task list for each Ansible playbook.

All playbooks are in ansible/playbooks/ and target the k8s_cluster host group by default.

update-system.yml

Purpose: Run apt update && apt upgrade on all nodes.

Usage:

cd ansible
ansible-playbook playbooks/update-system.yml
ansible-playbook playbooks/update-system.yml --limit control_nodes
ansible-playbook playbooks/update-system.yml -e "reboot_after_update=true"

Variables:

VariableDefaultDescription
reboot_after_updatefalseReboot after update if the system requires it
reboot_timeout300Timeout (seconds) to wait for reboot

Tasks:

  1. Update apt cache
  2. Upgrade all packages (dist-upgrade with autoremove)
  3. Check if reboot is required (/var/run/reboot-required)
  4. Reboot if required and enabled
  5. Wait for system to come back online

security-hardening.yml

Purpose: Apply security measures on all nodes.

Usage:

cd ansible
ansible-playbook playbooks/security-hardening.yml

Variables:

VariableDefaultDescription
enable_unattended_upgradestrueInstall and configure unattended-upgrades
configure_fail2bantrueVerify fail2ban is running

Tasks:

  1. Install and configure unattended-upgrades (security updates only)
  2. Enable automatic update checks (daily)
  3. Verify fail2ban is running and monitoring SSH
  4. Set secure permissions on /etc/shadow
  5. Disable core dumps
  6. Apply sysctl hardening (reverse path filtering, source route rejection, ASLR)

configure-nat64.yml

Purpose: Configure DNS64 resolvers and NAT64 routing on already-running nodes. Cloud-init only runs at first boot – use this playbook for existing nodes or to reconfigure.

Usage:

cd ansible
ansible-playbook playbooks/configure-nat64.yml
ansible-playbook playbooks/configure-nat64.yml --limit control_nodes
ansible-playbook playbooks/configure-nat64.yml -e '{"dns64_resolvers":["2a01:4f8:c2c:123f::1"]}'

Variables:

VariableDefaultDescription
dns64_resolvers["2a01:4f8:c2c:123f::1", "2a01:4f9:c010:3f02::1", "2a00:1098:2b::1"]DNS64 resolver addresses
nat64_prefix"64:ff9b::/96"NAT64 prefix

Tasks:

  1. Configure DNS64 resolvers in systemd-resolved
  2. Add NAT64 route via default IPv6 gateway
  3. Create networkd-dispatcher script for persistent route
  4. Update UFW rules on worker nodes (remove Hetzner DNS, add DNS64 + NAT64 rules)
  5. Verify DNS64 resolution and NAT64 connectivity

prepare-k8s-nodes.yml

Purpose: Install container runtime (containerd or CRI-O), kubeadm, kubelet, and kubectl on already-running nodes. Cloud-init only runs at first boot – use this playbook for existing nodes.

Usage:

cd ansible
ansible-playbook playbooks/prepare-k8s-nodes.yml
ansible-playbook playbooks/prepare-k8s-nodes.yml --limit control_nodes
ansible-playbook playbooks/prepare-k8s-nodes.yml -e "kubernetes_version=1.32"
ansible-playbook playbooks/prepare-k8s-nodes.yml -e "container_runtime=cri-o"

Variables:

VariableDefaultDescription
kubernetes_version"1.32"Kubernetes minor version for apt repo
container_runtime"containerd"Container runtime: "containerd" or "cri-o"

Tasks:

  1. Load kernel modules (overlay, br_netfilter)
  2. Set sysctl parameters (bridge-nf-call-iptables, ip_forward)
  3. Disable swap
  4. Install and configure container runtime (containerd with SystemdCgroup, or CRI-O from OBS repo)
  5. Add Kubernetes apt repository
  6. Install kubelet, kubeadm, kubectl (held at current version)
  7. Open UFW ports on control nodes (kubelet 10250, etcd 2379-2380)
  8. Verify kubeadm version and container runtime status

Ansible configuration

The ansible.cfg file configures:

  • Remote user: kubernetes-admin
  • Host key checking: disabled (nodes are ephemeral)
  • Privilege escalation: sudo NOPASSWD
  • SSH args: ForwardAgent, ControlMaster, ControlPersist for fast connections
  • Pipelining: enabled for performance

5 - OS Images

Hetzner Cloud OS images available for cluster nodes, and why the default is Debian 13.

Hetzner Cloud offers the following Debian/Ubuntu images:

ImageNameRecommendation
debian-13Debian 13 TrixieRecommended for K8s
debian-12Debian 12 BookwormStable, well-proven
ubuntu-24.04Ubuntu 24.04 LTSGood for K8s
ubuntu-22.04Ubuntu 22.04 LTSWell-proven

Why Debian 13?

  • Stability: long support cycles
  • Compatibility: the pkgs.k8s.io apt repository used for kubeadm, kubelet, and kubectl ships Debian packages
  • Small footprint: leaner than Ubuntu, but not as small as Alpine
  • No glibc/musl issues: unlike Alpine, no compatibility problems

Hetzner Cloud doesn’t provide dedicated “slim” or “minimal” variants. The standard images are fairly compact already.

6 - Project Structure

Repository layout and the purpose of each top-level file and directory.
kubeclaw/
├── main.tf                          # Infrastructure (providers, network, firewalls, servers)
├── variables.tf                     # All configurable inputs
├── outputs.tf                       # IPs, SSH keys, ssh_config_snippet, next-steps banner
├── terraform.tfvars.example         # Example configuration
├── hugo.yaml                        # Hugo + Docsy configuration
├── package.json                     # Docsy asset dependencies
├── themes/docsy/                    # Pinned Docsy theme submodule
├── aibox.toml                       # Dev Container source of truth (aibox apply)
├── aibox.lock                       # Pinned aibox toolchain versions
├── CLAUDE.md                        # Claude Code project instructions
├── README.md                        # Project overview (concise)
├── LICENSE                          # MIT License
├── .gitignore
├── .devcontainer/
│   ├── devcontainer.json            # Generated by aibox; do not edit directly
│   ├── Dockerfile                   # Generated by aibox; do not edit directly
│   ├── docker-compose.yml           # Generated by aibox; do not edit directly
│   ├── Dockerfile.local             # Project layer: installs cloudflared
│   └── docker-compose.override.yml  # Project override: writable SSH mount
├── cloud-init/
│   ├── admin-node.yaml.tpl         # Admin node (temporary jump host with public IPv6)
│   ├── control-node.yaml.tpl       # Control node (cloudflared on master, UFW, fail2ban, k8s prereqs)
│   └── worker-node.yaml.tpl        # Worker node (isolated, outbound DNS/HTTP/S only, k8s prereqs)
├── scripts/
│   ├── setup-ssh.sh                # Export SSH keys from tofu state, generate ~/.ssh/config
│   ├── ssh-agent-setup.sh          # Fix SSH permissions, start ssh-agent, load keys
│   ├── generate-ansible-inventory.sh  # Build ansible/inventory.ini from tofu state
│   ├── build-docs.sh              # Build Hugo site locally
│   ├── serve-docs.sh              # Preview Hugo site locally
│   └── deploy-docs.sh             # Build and deploy Hugo to gh-pages
├── ansible/
│   ├── ansible.cfg                  # Ansible defaults (user, pipelining, SSH args)
│   ├── inventory.ini                # Auto-generated inventory (do not hand-edit)
│   └── playbooks/
│       ├── update-system.yml        # System updates with optional reboot
│       ├── security-hardening.yml   # Unattended upgrades, fail2ban, sysctl hardening
│       ├── configure-nat64.yml      # NAT64/DNS64 for IPv4 reachability on running nodes
│       └── prepare-k8s-nodes.yml   # Kubernetes prerequisites (container runtime, kubeadm) on running nodes
├── assets/                         # Project brand layer for the Hugo site
│   ├── icons/logo.svg              # projectious mark used in the navbar
│   └── scss/                       # Brand tokens and Docsy surface styles
├── layouts/                        # Project template overrides (favicons, head hooks)
├── static/favicons/                # Brand favicon set
├── content/                        # Hugo documentation source
│   ├── _index.md                   # Branded landing page
│   └── docs/                       # Documentation section
│       ├── _index.md               # Documentation overview
│       ├── quick-start.md          # Combined prerequisites + deployment steps
│       ├── introduction/           # Concepts: architecture, security, DNS/NAT64
│       ├── guide/                  # Ordered deployment path (dev container -> OpenClaw)
│       ├── how-to/                 # Focused procedures, incl. the manual setup alternative
│       ├── reference/              # Variables, outputs, templates, playbooks, structure
│       ├── operations/             # Day-two: scaling, rotation, upgrades, troubleshooting
│       ├── roadmap/                # Planned work
│       └── contributing/           # Development setup and code of conduct
└── public/                         # Generated site output (gitignored)

Key Files

Infrastructure (root level)

FilePurpose
main.tfCore infrastructure: providers, SSH keys, network/subnet, firewalls, servers, cloud-init rendering
variables.tfAll configurable inputs with types, defaults, and descriptions
outputs.tfExposes IPs, SSH keys (sensitive), ssh_config_snippet, and next-steps banner
terraform.tfvars.exampleReference configuration (actual .tfvars is gitignored)

Cloud-Init Templates

FilePurpose
cloud-init/admin-node.yaml.tplMinimal jump host: admin user, SSH hardening, fail2ban, public SSH
cloud-init/control-node.yaml.tplControl plane: cloudflared (master only), UFW, fail2ban, optional NAT64 + K8s prereqs
cloud-init/worker-node.yaml.tplWorker: restrictive UFW, no TCP forwarding, optional NAT64 + K8s prereqs

Scripts

FilePurpose
scripts/setup-ssh.shExport SSH keys, generate ~/.ssh/config with backup
scripts/ssh-agent-setup.shFix SSH permissions, start ssh-agent, load keys
scripts/generate-ansible-inventory.shBuild Ansible inventory from OpenTofu state
scripts/build-docs.shBuild the Hugo site locally
scripts/serve-docs.shPreview the site locally
scripts/deploy-docs.shBuild Hugo and push to gh-pages

Dev Container

The aibox configuration in aibox.toml is the source of truth for generated Dev Container files. Run aibox apply after changing it; do not hand-edit .devcontainer/Dockerfile, docker-compose.yml, or devcontainer.json. Dockerfile.local and docker-compose.override.yml are project-owned extensions and are safe to edit.

Ansible

FilePurpose
ansible/ansible.cfgDefaults: remote user, host key checking, privilege escalation
ansible/inventory.iniAuto-generated inventory with control_nodes, worker_nodes, k8s_cluster groups

Documentation site

FilePurpose
hugo.yamlHugo + Docsy configuration, menus, version list, and module mounts
assets/scss/_variables_project.scssprojectious brand tokens mapped onto Bootstrap/Docsy variables
assets/scss/_styles_project.scssBrand surface styles: type scale, code theme, dark mode, chrome
assets/icons/logo.svgThe projectious mark inlined into the navbar
layouts/partials/favicons.htmlFavicon set override (Docsy’s default references assets this project does not ship)
layouts/partials/hooks/head-end.htmlBrand web-font loading
static/favicons/Brand favicon and touch-icon assets

7 - Cost Estimate

Indicative monthly Hetzner Cloud costs for common cluster sizes.

All figures are list prices in EUR excluding VAT, current as of the Hetzner price adjustment of 15 June 2026. Hetzner bills hourly; the monthly figure is the cap you will not exceed. Always check Hetzner Cloud pricing before committing – prices changed several times during 2026.

Server types

KubeClaw defaults to the cost-optimized CX line (shared vCPU, x86). The CAX line offers the same resources on Ampere Arm cores, but is only available in the German and Finnish locations (fsn1, nbg1, hel1) and requires arm64 container images throughout the cluster.

TypevCPURAMNVMeMonthly (excl. IPv4)
cx2324 GB40 GB€5.49
cx3348 GB80 GB€8.49
cx43816 GB160 GB€15.99
cx531632 GB320 GB€29.49
cax11 (Arm)24 GB40 GB€5.99
cax21 (Arm)48 GB80 GB€10.49
cax31 (Arm)816 GB160 GB€20.99
cax41 (Arm)1632 GB320 GB€40.99

The higher-performance CPX (dedicated AMD share) and CCX (fully dedicated) lines are considerably more expensive – cpx22 is €19.49/month and ccx13 is €42.99/month – and are rarely worth it for a learning or small-production cluster.

Additional costs

ItemPrice
Primary IPv4 address€0.50 per server per month
Block storage volume~€0.0572 per GB per month
Private networkFree
Cloudflare Tunnel + AccessFree (up to 50 users)

Example: 2-node cluster

ComponentSpecificationMonthly Cost
Master control node (cx23, IPv6-only)2 vCPU / 4 GB RAM€5.49
Worker node (cx23, IPv6-only)2 vCPU / 4 GB RAM€5.49
Block volumes (10 GB each)Hetzner CSI, 20 GB total~€1.14
Private networkFree
Cloudflare Tunnel + AccessUp to 50 usersFree
Total~€12.12/month

Scaling costs

Server cost only; add block volumes for any workload that needs persistent storage.

ConfigurationNodesServer cost
Master-only (dev/learning)1€5.49
Master + 1 worker2€10.98
3 control + 2 workers (HA)5€27.45

Notes

  • The admin node (cx23, €5.49/month) is temporary. Set enable_admin_node = false once the Cloudflare Tunnel works, and the charge stops – see Quick Start.
  • Mixed server types are supported, so workers can be sized independently of the control plane. See Scale Up/Down.
  • Snapshots and backups are billed separately and are not included above.