Skip to content

Instantly share code, notes, and snippets.

@stephankoelle
Created June 17, 2025 09:47
Show Gist options
  • Select an option

  • Save stephankoelle/271c98b5615db6b8c55bda418b98d8da to your computer and use it in GitHub Desktop.

Select an option

Save stephankoelle/271c98b5615db6b8c55bda418b98d8da to your computer and use it in GitHub Desktop.
Terraform for Hetzner Cloud Fedora CoreOS
# Alter:
# hcloud_server_type
# ssh_public_key_file
# ssh_private_key_file
# versions.tf
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
}
}
required_version = ">= 0.13"
}
#===========================================
#config.yaml :
# For docs, see: https://coreos.github.io/butane/specs/
variant: fcos
version: 1.5.0
passwd:
users:
- name: core
groups:
- docker
- wheel
- sudo
ssh_authorized_keys:
# Will be replaced by terraform script
- $ssh_public_key
#===========================================
# main.tf
#https://community.hetzner.com/tutorials/howto-hcloud-terraform-fedora-coreos
#https://releases.hashicorp.com/terraform/1.8.4/terraform_1.8.4_linux_amd64.zip
#https://console.hetzner.cloud/projects/2938711/security/tokens
#
#/terraform-coreos (main)$ /tmp/terraform init
#/terraform-coreos (main)$ /tmp/terraform apply
####
# Variables
##
variable "hcloud_token" {
description = "Hetzner Cloud API Token"
type = string
}
variable "ssh_public_key_file" {
description = "Local path to your public key"
type = string
default = "~/.ssh/id_rsa_hetzner.pub"
}
variable "ssh_private_key_file" {
description = "Local path to your private key"
type = string
default = "~/.ssh/id_rsa_hetzner"
}
variable "ssh_public_key_name" {
description = "Name of your public key to identify at Hetzner Cloud portal"
type = string
default = "My-SSH-Key"
}
variable "hcloud_server_type" {
description = "vServer type name, lookup via `hcloud server-type list`"
type = string
default = "cax11" #11 #41
}
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 = "plantpamper1"
}
# Update version to the latest release of Butane
variable "tools_butane_version" {
description = "See https://github.com/coreos/butane/releases for available versions"
type = string
default = "0.19.0"
}
####
# Infrastructure config
##
provider "hcloud" {
token = var.hcloud_token
}
resource "hcloud_ssh_key" "key" {
name = var.ssh_public_key_name
public_key = file(var.ssh_public_key_file)
}
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 = "fedora-39"
rescue = "linux64"
ssh_keys = [hcloud_ssh_key.key.id]
connection {
host = self.ipv4_address
timeout = "15m"
private_key = file(var.ssh_private_key_file)
# Root is the available user in rescue mode
user = "root"
}
# Wait for the server to be available
provisioner "local-exec" {
command = "until nc -zv ${self.ipv4_address} 22; do sleep 5; done"
}
# Copy config.yaml and replace $ssh_public_key variable
provisioner "file" {
content = replace(file("config.yaml"), "$ssh_public_key", trimspace(file(var.ssh_public_key_file)))
destination = "/root/config.yaml"
}
# Copy coreos-installer binary, as initramfs has not sufficient space to compile it in rescue mode
#provisioner "file" {
# source = "coreos-installer"
# destination = "/usr/local/bin/coreos-installer"
#}
# Install Butane in rescue mode
provisioner "remote-exec" {
inline = [
"set -x",
# Convert ignition yaml into json using Butane
#"wget -O /usr/local/bin/butane 'https://github.com/coreos/butane/releases/download/v${var.tools_butane_version}/butane-x86_64-unknown-linux-gnu'",
"wget -O /usr/local/bin/butane 'https://github.com/coreos/butane/releases/download/v${var.tools_butane_version}/butane-aarch64-unknown-linux-gnu'",
"chmod +x /usr/local/bin/butane",
"butane --strict < config.yaml > config.ign",
# coreos-installer binary is copied, if you have sufficient RAM available, you can also uncomment the following
# two lines and comment-out the `chmod +x` line, to build coreos-installer in rescue mode
"apt-get install -y podman",
# "cargo install coreos-installer",
##"apt-get install -y openssl libzstd-dev libssl-dev",
#"mount /dev/sda1 /mnt/",
"df -h; ls -la",
"podman run --net=host --pull=always --privileged --rm -v /dev:/dev -v /run/udev:/run/udev -v .:/data -w /data quay.io/coreos/coreos-installer:release install /dev/sda -i config.ign",
##"mkdir /mnt/cargoinst",
##"export CARGO_HOME=/mnt/cargoinst",
##"export RUSTUP_HOME=/mnt/cargoinst",
##"curl https://sh.rustup.rs -sSf | sh -s -- -y",
##". /mnt/cargoinst/env",
##"export CARGO_HOME=/mnt/cargoinst",
##"export RUSTUP_HOME=/mnt/cargoinst",
##"cargo install --target-dir /mnt/cargoinst/ coreos-installer",
##"chmod +x /mnt/cargoinst/bin/coreos-installer",
# Download and install Fedora CoreOS to /dev/sda
##"/mnt/cargoinst/bin/coreos-installer install /dev/sda -i /root/config.ign",
# Exit rescue mode and boot into coreos
"reboot"
]
}
# Wait for the server to be available
provisioner "local-exec" {
command = "until nc -zv ${self.ipv4_address} 22; do sleep 15; done"
}
# Configure CoreOS after installation
provisioner "remote-exec" {
connection {
host = self.ipv4_address
timeout = "1m"
private_key = file(var.ssh_private_key_file)
# This user is configured in config.yaml
user = "core"
}
inline = [
"sudo hostnamectl set-hostname ${self.name}"
# Add additional commands if needed
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment