Skip to content

steffend/nerves_hetzner_cloud

Nerves on Hetzner Cloud

To run Nerves on Hetzner Cloud, we cannot use the official x86_64 system, because it misses some kernel configuration, but I did not look into which ones exactly.

If you're interested in debugging, feel free to create a minimal nerves_system_hcloud or similar. For this, I'm using the nerves_containers_x86_64 system, which I verified to work. The downside is a rather large system size, as it includes a kernel based on Debian trixie, not optimized for cloud systems.

- {:nerves_system_x86_64, "~> 1.19", runtime: false, targets: :x86_64},
+ {:nerves_containers_x86_64, github: "nerves-containers/nerves_containers_x86_64", tag: "v1.31.4", runtime: false, targets: :x86_64},

Because Hetzner Cloud uses a network setup where the default gateway is outside of the local network and VintageNet currently does not expect this, you need to add a dependency on {:nerves_hetzner_cloud, github: "SteffenDE/nerves_hetzner_cloud", targets: @all_targets} to your mix.exs file. This will set the correct routes for you before your app starts.

# Dependencies for all targets except :host
{:nerves_runtime, "~> 0.13.0", targets: @all_targets},
{:nerves_pack, "~> 0.7.0", targets: @all_targets},
+ {:nerves_hetzner_cloud, github: "SteffenDE/nerves_hetzner_cloud", targets: @all_targets},

Note: I was lazy and did not look into IPv6 networking. Please open up a PR with instructions if you do and get it working!

If you want to get the IEx shell on the Hetzner console, you'll also need to change your target.exs config file:

- config :nerves, :erlinit, update_clock: true
+ config :nerves, :erlinit, update_clock: true, ctty: "tty1"

The difficult part is installing Nerves, because Hetzner Cloud does not support custom ISO images or PXE booting. There are three options:

Using cloud-init

A simple solution is to use a cloud-init script. Hetzner offers a textbox where you can enter a custom cloud-init script when creating a new server. The script I created expects the server to be created with a Ubuntu image, and then it immediately overwrites the disk and restarts into nerves.

See the cloud-init.yml file for the full script.

Important: you need to copy your firmware.fw file to a public location where the server can download it. Replace the FIRMWARE_URL variable at the bottom accordingly.

If you open the server console, you should see it briefly booting into Ubuntu, showing the login mask and then shortly after reboot into Nerves.

Running the flash script from a running system

You can also first create a server with a default Ubuntu image, then build your firmware and copy it using scp, and finally run the flash.sh script from the server:

$ MIX_TARGET=x86_64 MIX_ENV=prod mix firmware
$ scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null _build/x86_64_prod/nerves/images/my_nerves_project.fw root@your-server-ip:/tmp/firmware.fw
$ ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@your-server-ip "bash -c 'wget https://raw.githubu
sercontent.com/SteffenDE/nerves_hetzner_cloud/main/flash.sh -O /tmp/flash.sh && bash /tmp/flash.sh'"

Note: I added -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null to prevent you needing to remove the offensing lines from your ~/.ssh/known_hosts file every after flashing Nerves, because the Nerves system will have different SSH host keys.

Manual install from a Ubuntu Live Installer

Hetzner supports booting from a list of pre-defined ISO images. I've used a Ubuntu Server 24.04 Live installer.

First, create a server as usual. The OS you select does not matter. After it was created, shut it down again and go to the ISO-Images tab. Select the Ubuntu 24.04.1 Live Server image and mount it.

When booting, select "Try or Install Ubuntu Server" from the menu.

On the welcome screen, navigate to the top right help menu and select "Enter shell". You can also go through the first install step steps to select a different keyboard layout and then enter the shell.

In the shell, type:

wget https://github.com/fwup-home/fwup/releases/download/1.13.2/fwup_1.13.2_amd64.deb
dpkg -i fwup_1.13.2_amd64.deb

Next, download your firmware image. You'll need to put it somewhere accessible and download it using wget.

Then, assuming your firmware file is called firmware.fw and is in the current working directory, type:

$ fwup -a -i firmware.fw -d /dev/sda -t complete
$ reboot

When prompted to remove the installation media, go back to the ISO-Images tab in the Hetzner console and unmount the ISO image.

Updating a running system

To update a running system, you can use the regular SSH based approach, e.g.

$ MIX_TARGET=x86_64 MIX_ENV=prod mix firmware
$ MIX_TARGET=x86_64 MIX_ENV=prod mix upload root@your-server-ip