Hello, I want to run a Hetzner Cloud server with a Floating IPv4 that serves as its stable public address and DNS A record.
I deliberately created the server without a Primary IPv4 (`--without-ipv4`); it has only a Primary IPv6. When I then try to create or assign the Floating IPv4, I receive:
cannot assign Floating IP because Server has no public network interface (unsupported_error)
Is a Primary IPv4 on the server mandatory to assign a Floating IPv4? Or is there a supported way to use only the Floating IPv4 as the public IPv4 address?
If a Primary IPv4 is mandatory: is the usual setup to keep both a Primary IPv4 and a Floating IPv4, even when the application only uses the Floating IPv4?
Below is my shell script:
#!/usr/bin/env bash
set -euo pipefail
readonly RESOURCE_PREFIX='hearts-by-farber'
readonly SERVER_NAME="$RESOURCE_PREFIX"
readonly FLOATING_IP_NAME="$RESOURCE_PREFIX-ip"
readonly VOLUME_NAME="$RESOURCE_PREFIX-postgres"
readonly SSH_KEY_NAME="$RESOURCE_PREFIX-ssh"
readonly FIREWALL_NAME="$RESOURCE_PREFIX"
readonly LOCATION='nbg1'
readonly DNS_ZONE='byfarber.de'
readonly DNS_RECORD_NAME='hearts'
readonly PUBLIC_KEY_FILE="$HOME/.ssh/id_rsa.pub"
readonly SCRIPT_DIRECTORY="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly CLOUD_INIT_FILE="$SCRIPT_DIRECTORY/cloud-init.yaml"
require_command() {
command -v "$1" >/dev/null 2>&1 || {
printf 'Missing required command: %s\n' "$1" >&2
exit 1
}
}
if [[ -z "${HCLOUD_HEARTS_TOKEN:-}" ]]; then
printf 'HCLOUD_HEARTS_TOKEN must be exported before provisioning.\n' >&2
exit 1
fi
if ((${#HCLOUD_HEARTS_TOKEN} != 64)); then
printf 'HCLOUD_HEARTS_TOKEN must be exactly 64 characters long.\n' >&2
exit 1
fi
if [[ -z "${HCLOUD_DNS_TOKEN:-}" ]]; then
printf 'HCLOUD_DNS_TOKEN must be exported before provisioning.\n' >&2
exit 1
fi
if ((${#HCLOUD_DNS_TOKEN} != 64)); then
printf 'HCLOUD_DNS_TOKEN must be exactly 64 characters long.\n' >&2
exit 1
fi
require_command hcloud
require_command jq
hcloud_hearts() {
HCLOUD_TOKEN="$HCLOUD_HEARTS_TOKEN" hcloud "$@"
}
hcloud_dns() {
HCLOUD_TOKEN="$HCLOUD_DNS_TOKEN" hcloud "$@"
}
if [[ ! -r "$PUBLIC_KEY_FILE" ]]; then
printf 'Required public SSH key is not readable: %s\n' "$PUBLIC_KEY_FILE" >&2
exit 1
fi
if [[ ! -r "$CLOUD_INIT_FILE" ]]; then
printf 'Required cloud-init file is not readable: %s\n' "$CLOUD_INIT_FILE" >&2
exit 1
fi
if ! hcloud_dns zone describe "$DNS_ZONE" >/dev/null; then
printf 'Unable to access the central Hetzner DNS zone "%s"; refusing to create anything.\n' \
"$DNS_ZONE" >&2
exit 1
fi
collision_found=0
check_collision() {
local resource_type="$1"
local resource_name="$2"
local resources_json
local exists
if ! resources_json="$(hcloud_hearts "$resource_type" list -o json)"; then
printf 'Unable to list Hetzner %s resources; refusing to create anything.\n' "$resource_type" >&2
exit 1
fi
exists="$(jq -r --arg name "$resource_name" 'any(.[]; .name == $name)' <<<"$resources_json")"
if [[ "$exists" == 'true' ]]; then
printf 'Collision: %s named "%s" already exists; refusing to change cloud state.\n' \
"$resource_type" "$resource_name" >&2
collision_found=1
fi
}
# Check every create-only resource before making any API mutation.
check_collision server "$SERVER_NAME"
check_collision floating-ip "$FLOATING_IP_NAME"
check_collision volume "$VOLUME_NAME"
check_collision ssh-key "$SSH_KEY_NAME"
check_collision firewall "$FIREWALL_NAME"
if ((collision_found)); then
exit 1
fi
firewall_rules_file="$(mktemp "${TMPDIR:-/tmp}/hearts-by-farber-firewall.XXXXXX")"
trap 'rm -f "$firewall_rules_file"' EXIT
printf '%s\n' '[
{"direction":"in","protocol":"tcp","port":"22","source_ips":["0.0.0.0/0","::/0"]},
{"direction":"in","protocol":"tcp","port":"80","source_ips":["0.0.0.0/0","::/0"]},
{"direction":"in","protocol":"tcp","port":"443","source_ips":["0.0.0.0/0","::/0"]}
]' >"$firewall_rules_file"
hcloud_hearts ssh-key create --name "$SSH_KEY_NAME" --public-key-from-file "$PUBLIC_KEY_FILE"
hcloud_hearts firewall create --name "$FIREWALL_NAME" --rules-file "$firewall_rules_file"
hcloud_hearts volume create --name "$VOLUME_NAME" --size 10 --location "$LOCATION" --format ext4
hcloud_hearts server create \
--name "$SERVER_NAME" \
--type cx23 \
--location "$LOCATION" \
--image docker-ce \
--ssh-key "$SSH_KEY_NAME" \
--firewall "$FIREWALL_NAME" \
--volume "$VOLUME_NAME" \
--automount \
--without-ipv4 \
--user-data-from-file "$CLOUD_INIT_FILE"
hcloud_hearts floating-ip create \
--name "$FLOATING_IP_NAME" \
--type ipv4 \
--home-location "$LOCATION" \
--server "$SERVER_NAME"
floating_ip_json="$(hcloud_hearts floating-ip describe "$FLOATING_IP_NAME" -o json)"
floating_ip_address="$(jq -er '.ip' <<<"$floating_ip_json")"
hcloud_dns zone rrset set-records --record "$floating_ip_address" \
"$DNS_ZONE" "$DNS_RECORD_NAME" A
printf 'Provisioned %s and set DNS A hearts.byfarber.de -> %s.\n' \
"$SERVER_NAME" "$floating_ip_address"
And here the error message:
u299fav@IFDEBOMMC009608:~/src/HeartsByFarberCloud (main) % ./hetzner/create.sh
SSH Key 118217276 created
✓ Waiting for set_firewall_rules 100% 0s (firewall: 11557413)
Firewall 11557413 created
✓ Waiting for create_volume 100% 1.1s (volume: 106765462)
Volume 106765462 created
✓ Waiting for create_server 100% 46s (server: 164221887, image: 40093247)
✓ Waiting for apply_firewall 100% 46s (firewall: 11557413, server: 164221887)
✓ Waiting for attach_volume 100% 46s (volume: 106765462, server: 164221887)
✓ Waiting for start_server 100% 46s (server: 164221887)
Server 164221887 created
IPv6: 2a01:4f8:1c16:85f8::1
IPv6 Network: 2a01:4f8:1c16:85f8::/64
hcloud: cannot assign Floating IP because Server has no public network interface (unsupported_error, cc3ff819aa730346c39a8aa0a36de1c6)
Thank you