Hetzner Binding
The Hetzner binding deploys the canonical Rumi private cloud onto Hetzner Cloud. The same Rumi application, described once in DDL, runs unchanged across a developer laptop, local Docker, AWS, and Hetzner; the Hetzner binding is the component that turns that model into real networks, firewalls, servers, and DNS.
It follows the same prebaked-image shape as the AWS binding (bake a role once, boot many nodes from it), adapted to Hetzner's primitives: private networks, label-selected cloud firewalls, server snapshots, and a bastion that doubles as the fleet's DNS server.
This page is a feature catalogue. For the commands that drive each feature, see the rumi cloud hetzner reference.
The Hetzner binding is live-verified end to end: provision β deploy β lifecycle β expose β HTTPS with a real Let's Encrypt wildcard certificate.
Deployment model
Hetzner uses a prebaked-snapshot model, the same shape as the AWS AMI model. Each role's software is baked once into a Hetzner snapshot via create_image, and every launch boots a node from that snapshot; launches select the right snapshot by label. Every non-worker role is baked from the service (Worker base) snapshot, mirroring "build on the Rumi Worker AMI," so a role recipe installs only its delta.
The seven roles and how each boots:
service
The Worker base: rumi user, Java 8/11/17, a boot.sh extension point run by a rumi-boot.service systemd unit at boot. No agent.
admin
ActiveMQ discovery broker + agent + admin server (via the official versioned installers), discovery pinned to activemq://localhost:61616.
monitor
Grafana (neeve/nvx-rumi-monitor) as a restart-always container.
solace
Solace PubSub+ standard as a restart-always container.
kafka
Kafka in KRaft mode as a restart-always container.
activemq
Native ActiveMQ as the rumi-messaging broker (distinct from the admin node's rumi-discovery).
bastion
The public entry point and internal DNS server (see below).
Network topology
The topology is public-only: every node has a public IP and also joins a private network (10.0.0.0/16, subnet 10.0.0.0/24). Only the bastion is reachable from the internet; all other roles open their ports only to the network CIDR, so every fleet operation flows through the bastion.
create-networkcreates the private network plus seven per-role cloud firewalls that auto-apply by label selector (rumi-network=<name>,rumi-role=<role>). The bastion firewall opens22/80/443to the world; every other role opens its ports to the network CIDR; all roles allow in-network ICMP.Because firewalls apply by label, a launch is secured the instant its labels are set β the per-service app-port firewall can be created before or after the instance it protects.
A managed public-DNS + NAT / private-only-egress topology (the equivalent of the AWS public-private-nat topology) is on the roadmap. Today every node is public and on the private network.
Bastion model
DNS server: the bastion runs BIND authoritative for the
rumi.localzone, mapping each role's short internal names to its private (10.x) IP.adminalso getsdiscoveryandinfluxdbnames. Each launched node's resolver is pointed at the bastion's private IP, and the bastion fans out over the private network.Public entry point: only the bastion is SSH-reachable from outside; the private key is copied to the bastion (
~/.ssh/rumi) so it can reach the rest of the fleet.Reverse proxy fronting: each service's public port is served through the bastion's nginx. It must be launched before any other role.
First-boot guard:
launchBastionwaits for:80before the platform launch continues, so DNS and the proxy are ready before dependent roles register.
Service instances
launch-instanceboots a worker from theservicesnapshot, labelledrumi-role=serviceandrumi-service=<network>-<name>, so both the base service firewall (:22) and a per-service app-port firewall auto-apply. It then registers<service>.rumi.local.Dynamic app-port opening: the per-service firewall opens the requested ports to the network CIDR and applies by label, so ports can be opened before or after launch. It is labelled with the network so teardown sweeps it.
HTTPS
Let's Encrypt wildcard certificates via certbot's DNS-01 challenge, with autonomous renewal. One domain per bastion; the bastion's nginx terminates TLS for every service it proxies.
Provider-pluggable DNS-01: the public DNS for the domain can live wherever you keep it β
--dns-provider route53 | hetzner | cloudflareselects the matchingcertbot-dns-<provider>plugin (all three are baked into the bastion). Credentials are written root-only on the bastion and reused for hands-free renewal.Run
enable-httpsright after launching the bastion and before adding service proxies, so each proxy is created as a TLS block.
Rumi does not manage public DNS. Pointing the public name at the bastion β *.<domain> or <service>.<domain> β the bastion's public IP β is the operator's job, in whatever DNS hosts the domain. enable-https only proves control of the domain (the DNS-01 challenge) to issue the certificate.
Lifecycle and idempotency
Granular commands launch the bastion, platform services, brokers, and workers individually; convenience commands (
provision,launch-platform) stand up a whole single-bastion deployment in one step.Per-component lifecycle:
start-admin,stop-solace,terminate-monitor, and the rest give every role and service its ownstart-*/stop-*/terminate-*verb (the same vocabulary as AWS and Civo).start-platform/stop-platform/terminate-platformwalk the whole platform in dependency order (bastion first on start, last on stop/terminate), and the env-widestart/stopact on every instance in the network.resize-instanceretypes a stopped server (stop βchange_typeβ restart, since Hetzner requires the server off to retype).Idempotent teardown:
deprovisiondeletes the servers, waits for them to be gone (a label-applied firewall cannot be deleted while still attached β Hetzner rejects it), then deletes the firewalls and the network, with no leaks. Terminating a service also removes its per-service firewall after the server is gone.
High-level operations
deploycopies the XAR to the bastion staging directory and hands it to the admin agent.configureadds, updates, or removes a controller config property on the admin node.add-proxyadds a bastion reverse-proxy entry so a service is reachable at<service>.<domain>(nginx only β it does not touch public DNS).
Getting started
Set a Hetzner Cloud project API token:
export HCLOUD_TOKEN=...(or pass--api-key).Bake the role snapshots into the project (the
servicesnapshot first, since other roles bake from it).Have an SSH key pair; the private key lives at
~/.ssh/<key-pair>and its public half is uploaded automatically if absent.
Related documentation
AWS Binding β the same private cloud on AWS (SOC 2 posture, SSM, NAT topology, CloudWatch).
Civo Binding β the install-at-boot binding on Civo.
rumi cloud hetznerβ command reference.
Last updated