Install Fedora CoreOS on Hetzner using Terraform

There is an exellent guide published by Hetzner about this subject. This article is a update on that. Instead of producing the ignition file in the rescue environment at Hetzner, I create it locally and and provision it to the server.

Create a directory for your project first, example ~/hetzner/terraform-coreos.

I am using terraform provided as a snap in ubuntu.

Step 1. Install Terraform provider for Hetzner Cloud

Create a file with the following content:


terraform {
  required_providers {
    hcloud = {
      source = "hetznercloud/hcloud"
  required_version = ">= 0.13"

Now execute terraform init to install the Terraform Hetzner Cloud Provider.

Step 2. Build coreos-installer

coreos-installer is built using rust, and recent versions require quite new version of rust compiler. It cannot be built in the rescue environment at Hetzner as there is not enough space available. The most convenient way is to build it on your own computer using the official containers of rust and transfer the binary to the rescue environment. The rescue environment at Hetzner is now using Debian bookworm, so build coreos-installer using container using docker or podman. coreos-installer also needs some libraries to be installed in the container to build successfully.

$ podman run --rm --hostname rustbuilder -ti -v $PWD:/work /bin/bash

root@rustbuilder:/# apt update && apt install -y pkg-config libssl-dev libzstd-dev
root@rustbuilder:/# cargo install --target-dir . coreos-installer
root@rustbuilder:/# cp /usr/local/cargo/bin/coreos-installer /work/

Step 3. Terraform configuration

To instruct Terraform how to setup and configure your servers, create a file:


# Variables

variable "hcloud_token" {
  description = "Hetzner Cloud API Token"
  type = string

variable "hcloud_server_type" {
  description = "vServer type name, lookup via `hcloud server-type list`"
  type = string
  default = "cpx11"

variable "hcloud_server_datacenter" {
  description = "Desired datacenter location name, lookup via `hcloud datacenter list`"
  type = string
  default = "hel1-dc2"

variable "hcloud_server_name" {
  description = "Name of the server"
  type = string
  default = "www1"

variable "hcloud_ssh_key_id" {
  description = "Name of already uploaded ssh key at Hetzner"
  type = string

# Infrastructure config

provider "hcloud" {
  token = var.hcloud_token

resource "hcloud_server" "master" {
  name = var.hcloud_server_name
  labels = { "os" = "coreos" }

  server_type = var.hcloud_server_type
  datacenter = var.hcloud_server_datacenter

  # Image is ignored, as we boot into rescue mode, but is a required field
  image = 107768015
  rescue = "linux64"
  ssh_keys = [ var.hcloud_ssh_key_id ]

  connection {
    host = hcloud_server.master.ipv4_address
    timeout = "5m"
    agent = true
    # Root is the available user in rescue mode
    user = "root"

  # Copy config.ign
  provisioner "file" {
    source = "config.ign"
    destination = "/root/config.ign"

  # Copy coreos-installer binary
  provisioner "file" {
    source = "coreos-installer"
    destination = "/usr/local/bin/coreos-installer"

  # Install Fedora CoreOS in rescue mode
  provisioner "remote-exec" {
    inline = [
      "set -x",
      "chmod +x /usr/local/bin/coreos-installer",
      # Download and install Fedora CoreOS to /dev/sda
      "coreos-installer install /dev/sda -i /root/config.ign",
      # Exit rescue mode and boot into coreos

  # Configure CoreOS after installation
  provisioner "remote-exec" {
    connection {
      host = hcloud_server.master.ipv4_address
      timeout = "1m"
      agent = true
      # This user is configured in config.ign
      user = "core"

    inline = [
      "sudo hostnamectl set-hostname ${}"
      # Add additional commands if needed

Additionally we need a Fedore CoreOS Configuration file named config.yaml

# For docs, see:

variant: fcos
version: 1.1.0

    - name: core
        - docker
        - wheel
        - sudo
        # add all keys that you need for the server
        - ssh-ed25519 AAAA.... name-of-ed25519-key
        - ssh-rsa AAAA.... name-of-rsa-key
      # If you need ssh password login, generate hash via `openssl passwd -1` and insert it below
      # password_hash: $1$1234567890abcdef...

    - path: /etc/profile.d/
      overwrite: true
        inline: |
          export EDITOR=vim
    - path: /etc/localtime
      target: ../usr/share/zoneinfo/Europe/Helsinki

    # Installing vim, open-vm-tools and rsync as a layered package with rpm-ostree
    - name: rpm-ostree-install-software.service
      enabled: true
      contents: |
        Description=Layer vim with rpm-ostree
        # We run before `zincati.service` to avoid conflicting rpm-ostree
        # transactions.

        # `--allow-inactive` ensures that rpm-ostree does not return an error
        # if the package is already installed. This is useful if the package is
        # added to the root image in a future Fedora CoreOS release as it will
        # prevent the service from failing.
        ExecStart=/usr/bin/rpm-ostree install --apply-live --allow-inactive vim open-vm-tools rsync
        ExecStart=/bin/touch /var/lib/%N.stamp


In the section Variables of the, adjust the default values for hcloud_server_type, hcloud_server_datacenter and hcloud_server_name to your needs or add a terraform.tfvars file to set your desired settings.

Optionally, have a look at the config.yaml. This file will be used by the coreos-installer to configure the OS. For the first try, you should leave it as it is. But after successful provisioning, you want to add systemd service definitions to run containers on it.

The yaml file needs to become an ignition file using butane tool. I use a container here again with podman to accomplish that:

podman run --rm --interactive --security-opt label=disable --volume ${PWD}:/pwd --workdir /pwd --pretty --strict config.yaml > config.ign

Step 4 – Get it up and running!

Run terraform apply, enter your Hetzner Cloud API Token if prompted, and let the magic be done!

After a successful installation, you will see the message Apply complete! Resources: 2 added, 0 changed, 0 destroyed..

terraform show now outputs you all information about your new machine:

# hcloud_server.master: (tainted)
resource "hcloud_server" "master" {
    allow_deprecated_images    = false
    backups                    = false
    datacenter                 = "hel1-dc2"
    delete_protection          = false
    firewall_ids               = []
    id                         = "12345678"
    ignore_remote_firewall_ids = false
    image                      = "fedora-38"
    ipv4_address               = ""
    ipv6_address               = "2a01:xxxx:xxxx:xxxx::1"
    ipv6_network               = "2a01:xxxx:xxxx:xxxx::/64"
    keep_disk                  = false
    labels                     = {
        "os" = "coreos"
    location                   = "hel1"
    name                       = "www1"
    rebuild_protection         = false
    rescue                     = "linux64"
    server_type                = "cpx11"
    ssh_keys                   = [
    status                     = "running"

Step 5 – Do it again

You can now incrementally fill the two configuration files, to setup your services/containers. Run terraform destroy, confirm with yes and now adjust the files and recreate the infrastructure via terraform apply. But keep a look at the Hetzner Cloud Portal to ensure that there haven’t been spawned any “ghost machines”.


You have now a fully working Fedora CoreOS machine at Hetzner Cloud, set up with Terraform.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.