Deploy VM Instances on Hetzner Cloud with Terraform

Posted on 98 views

In this blog post, we will look at how to Provision VMs on Hetzner Cloud with Terraform. Hetzner is a hosting provider based in Germany which provides flexible cloud servers with high-end-hardware and auctions for Dedicated physical servers. Their pricing is very competitive with a per/month basis.

I use Hetzner for most of my hosting services and for building test labs. Working with Terraform ensures efficiency and a faster way to bring services to production. Terraform is an open-source infrastructure as code software tool created by HashiCorp.

Terraform allows you to safely and predictably create, change, and improve infrastructure. All your infrastructure code can be saved in a Git repository and versioned.

Lab Setup

In this article, I’ll show you how to create three instances on Hetzner Cloud using Terraform. We will add an ssh key to the instances used for remote access. The three VMs created will be from CentOS 7, Ubuntu 22.04 and Debian 11 templates. We will ensure terraform outputs the public IP addresses for the virtual machines created.

Key notes:

  • Available locations: nbg1, fsn1, hel1 or ash

Step 1: Install Terraform

Use our guide below to install Terraform in your Linux / Windows system.

Step 2: Create Terraform Project

Let’s create a folder for Terraform projects.

mkdir -p  ~/automation/terraform/hetzner
cd ~/automation/terraform/hetzner

Now create Terraform main configuration file.

touch main.tf

Step 3: Generate Hetzner API Token

Obtain API token from Hetzner console that will be used by Terraform to interact with the platform. Navigate to https://console.hetzner.cloud/projects and click on Access > API TOKENS > GENERATE API

Provide a name, permission and generate token.

hetzner-generate-token-01-1024x668

The output will be printed to the screen. Save it in a safe place for later use.

hetzner-generate-token-02-1024x483

Give token a descriptive name and hit Generate button. Note the API token generated as this will be used in the next section.

Step 4: Add SSH Key to Hetzner.

If you don’t have an ssh key, generate it.

$ ssh-keygen -q -N "" 
Enter file in which to save the key (/home/myuser/.ssh/id_rsa):

Copy the contents in ~/.ssh/id_rsa.pub

$ xclip -sel clip ~/.ssh/id_rsa.pub

Login to Hetzner console and add your ssh keys to Access > SSH KEYS > ADD SSH KEY

hetzner-add-ssh-key

Copy the fingerprint generated after adding the key, something like de:c7:80:23:5b:3e:28:52:1a:5d:0f:84:1b:fe:38:ec.

Step 5: Create and Populate Terraform configuration file

Edit the Terraform configuration file and add data used for creating resources.

############## Variables ###############
# Token variable
variable "hcloud_token" 
default = "PASTE_API_TOKEN_HERE"


# Define provider
terraform 
  required_providers 
    hcloud = 
      source = "hetznercloud/hcloud"
    
  


# Define Hetzner provider token
provider "hcloud" 
  token = var.hcloud_token


# Obtain ssh key data
data "hcloud_ssh_key" "ssh_key" 
  fingerprint = "PASTE_ADDED_SSH_KEY_FINGERPRINT_HERE"


# Create Debian 11 server
resource "hcloud_server" "debian11" 
  name = "debian11.computingpost.com"
  image = "debian-11"
  server_type = "cx21"
  ssh_keys  = ["$data.hcloud_ssh_key.ssh_key.id"]


# Output Server Public IP address 
output "server_ip_debian11" 
 value = "$hcloud_server.debian11.ipv4_address"

Initialize a Terraform working directory:

$ terraform  init
Initializing the backend...

Initializing provider plugins...
- Finding latest version of hetznercloud/hcloud...
- Installing hetznercloud/hcloud v1.33.2...
- Installed hetznercloud/hcloud v1.33.2 (signed by a HashiCorp partner, key ID 5219EACB3A77198B)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Terraform will automatically download provider to .terraform directory.

$ tree .terraform/
.terraform/
└── providers
    └── registry.terraform.io
        └── hetznercloud
            └── hcloud
                └── 1.33.2
                    └── darwin_amd64
                        ├── CHANGELOG.md
                        ├── LICENSE
                        ├── README.md
                        └── terraform-provider-hcloud_v1.33.2

6 directories, 4 files

To build your Infrastructure with Terraform, run terraform apply.

$ terraform apply

Sample output.

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # hcloud_server.debian11 will be created
  + resource "hcloud_server" "debian11" 
      + backup_window              = (known after apply)
      + backups                    = false
      + datacenter                 = (known after apply)
      + delete_protection          = false
      + firewall_ids               = (known after apply)
      + id                         = (known after apply)
      + ignore_remote_firewall_ids = false
      + image                      = "debian-11"
      + ipv4_address               = (known after apply)
      + ipv6_address               = (known after apply)
      + ipv6_network               = (known after apply)
      + keep_disk                  = false
      + location                   = (known after apply)
      + name                       = "debian11.computingpost.com"
      + rebuild_protection         = false
      + server_type                = "cx21"
      + ssh_keys                   = [
          + "3233336",
        ]
      + status                     = (known after apply)
    

Plan: 1 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + server_ip_debian11 = "hcloud_server.debian11.ipv4_address"
hcloud_server.debian11: Creating...
hcloud_server.debian11: Still creating... [10s elapsed]
hcloud_server.debian11: Creation complete after 10s [id=21530663]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:
server_ip_debian11 = "65.109.2.115"

Test access to your instances with printed IP addresses.

$ ssh [email protected]65.109.2.115
Warning: Permanently added '65.109.2.115' (ED25519) to the list of known hosts.
Linux debian11 5.10.0-14-amd64 #1 SMP Debian 5.10.113-1 (2022-04-29) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
[email protected]:~#

Other examples using different templates

Ubuntu 22.04:

# Create Ubuntu server
resource "hcloud_server" "ubuntu" 
  name = "ubuntu.computingpost.com"
  image = "ubuntu-22.04"
  server_type = "cx21"
  ssh_keys  = ["$data.hcloud_ssh_key.ssh_key.id"]


# Output Server Public IP address
output "server_ip_ubuntu" 
 value = "$hcloud_server.ubuntu.ipv4_address"

Ubuntu 20.04:

# Create Ubuntu server
resource "hcloud_server" "ubuntu" 
  name = "ubuntu.computingpost.com"
  image = "ubuntu-20.04"
  server_type = "cx21"
  ssh_keys  = ["$data.hcloud_ssh_key.ssh_key.id"]


# Output Server Public IP address
output "server_ip_ubuntu" 
 value = "$hcloud_server.ubuntu.ipv4_address"

Ubuntu 18.04:

# Create Ubuntu server
resource "hcloud_server" "ubuntu" 
  name = "ubuntu.computingpost.com"
  image = "ubuntu-18.04"
  server_type = "cx21"
  ssh_keys  = ["$data.hcloud_ssh_key.ssh_key.id"]


# Output Server Public IP address
output "server_ip_ubuntu" 
 value = "$hcloud_server.ubuntu.ipv4_address"

Debian 10:

# Create Debian server
resource "hcloud_server" "debian" 
  name = "debian.computingpost.com"
  image = "debian-10"
  server_type = "cx21"
  ssh_keys  = ["$data.hcloud_ssh_key.ssh_key.id"]


# Output Server Public IP address 
output "server_ip_debian" 
 value = "$hcloud_server.debian.ipv4_address"

CentOS 7:

# Create CentOS server
resource "hcloud_server" "centos" 
  name = "centos.computingpost.com"
  image = "centos-7"
  server_type = "cx21"
  ssh_keys  = ["$data.hcloud_ssh_key.ssh_key.id"]


# Output Server Public IP address
output "server_ip_centos" 
 value = "$hcloud_server.centos.ipv4_address"

Server creation with network

This is an example for VM creation with network creation:

resource "hcloud_network" "network" 
  name     = "network"
  ip_range = "10.0.0.0/16"


resource "hcloud_network_subnet" "network-subnet" 
  type         = "cloud"
  network_id   = hcloud_network.network.id
  network_zone = "eu-central"
  ip_range     = "10.0.1.0/24"


resource "hcloud_server" "server" 
  name        = "server"
  server_type = "cx11"
  image       = "ubuntu-22.04"
  location    = "nbg1"
  ssh_keys  = ["$data.hcloud_ssh_key.ssh_key.id"]

  network 
    network_id = hcloud_network.network.id
    ip         = "10.0.1.5"
    alias_ips  = [
      "10.0.1.6",
      "10.0.1.7"
    ]
  

  # **Note**: the depends_on is important when directly attaching the
  # server to a network. Otherwise Terraform will attempt to create
  # server and sub-network in parallel. This may result in the server
  # creation failing randomly.
  depends_on = [
    hcloud_network_subnet.network-subnet
  ]



# Output Server Public IP address
output "server_ip" 
 value = "$hcloud_server.server.ipv4_address"

SSH to server created and confirm network attachment:

[email protected]:~# ip ad
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0:  mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 96:00:01:5e:50:03 brd ff:ff:ff:ff:ff:ff
    altname enp0s3
    altname ens3
    inet 167.235.75.252/32 metric 100 scope global dynamic eth0
       valid_lft 86384sec preferred_lft 86384sec
    inet6 2a01:4f8:c2c:d66c::1/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::9400:1ff:fe5e:5003/64 scope link
       valid_lft forever preferred_lft forever
3: ens10:  mtu 1450 qdisc fq_codel state UP group default qlen 1000
    link/ether 86:00:00:15:8c:29 brd ff:ff:ff:ff:ff:ff
    altname enp0s10
    inet 10.0.1.5/32 brd 10.0.1.5 scope global dynamic ens10
       valid_lft 86389sec preferred_lft 86389sec
    inet6 fe80::8400:ff:fe15:8c29/64 scope link
       valid_lft forever preferred_lft forever

Destroying Terraform Infrastructure

To destroy Terraform-managed infrastructure, run the command.

$ terraform  destroy
data.hcloud_ssh_key.ssh_key: Refreshing state...
hcloud_server.centos7: Refreshing state... [id=2869955]
hcloud_server.ubuntu18: Refreshing state... [id=2869954]
hcloud_server.debian9: Refreshing state... [id=2869956]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # hcloud_server.centos7 will be destroyed
  - resource "hcloud_server" "centos7" 
      - backups      = false -> null
      - datacenter   = "nbg1-dc3" -> null
      - id           = "2869955" -> null
      - image        = "centos-7" -> null
      - ipv4_address = "116.203.44.172" -> null
      - ipv6_address = "2a01:4f8:c2c:83a2::" -> null
      - ipv6_network = "2a01:4f8:c2c:83a2::/64" -> null
      - keep_disk    = false -> null
      - location     = "nbg1" -> null
      - name         = "centos7" -> null
      - server_type  = "cx31" -> null
      - ssh_keys     = [
          - "421205",
        ] -> null
      - status       = "running" -> null
    

  # hcloud_server.debian9 will be destroyed
  - resource "hcloud_server" "debian9" 
      - backups      = false -> null
      - datacenter   = "nbg1-dc3" -> null
      - id           = "2869956" -> null
      - image        = "debian-9" -> null
      - ipv4_address = "116.203.87.93" -> null
      - ipv6_address = "2a01:4f8:c2c:44a6::" -> null
      - ipv6_network = "2a01:4f8:c2c:44a6::/64" -> null
      - keep_disk    = false -> null
      - location     = "nbg1" -> null
      - name         = "debian9" -> null
      - server_type  = "cx21" -> null
      - ssh_keys     = [
          - "421205",
        ] -> null
      - status       = "running" -> null
    

  # hcloud_server.ubuntu18 will be destroyed
  - resource "hcloud_server" "ubuntu18" 
      - backups      = false -> null
      - datacenter   = "nbg1-dc3" -> null
      - id           = "2869954" -> null
      - image        = "ubuntu-18.04" -> null
      - ipv4_address = "116.203.48.203" -> null
      - ipv6_address = "2a01:4f8:c2c:1006::" -> null
      - ipv6_network = "2a01:4f8:c2c:1006::/64" -> null
      - keep_disk    = false -> null
      - location     = "nbg1" -> null
      - name         = "ubuntu16" -> null
      - server_type  = "cx11" -> null
      - ssh_keys     = [
          - "421205",
        ] -> null
      - status       = "running" -> null
    

Plan: 0 to add, 0 to change, 3 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

hcloud_server.debian9: Destroying... [id=2869956]
hcloud_server.centos7: Destroying... [id=2869955]
hcloud_server.ubuntu18: Destroying... [id=2869954]
hcloud_server.centos7: Destruction complete after 0s
hcloud_server.ubuntu18: Destruction complete after 0s
hcloud_server.debian9: Destruction complete after 0s

When prompted to accept, type “yes“.

If you don’t want confirmation prompt, use:

terraform destroy -auto-approve

Check other Terraform resources and supported data types for Hetzner cloud.

coffee

Gravatar Image
A systems engineer with excellent skills in systems administration, cloud computing, systems deployment, virtualization, containers, and a certified ethical hacker.