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 18.04 and Debian 9 templates. We will ensure terraform outputs the public IP addresses for the virtual machines created.

Step 1: Install Terraform

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

Install Terraform on Windows 10 / Windows Server 2019

How To Install Terraform on Linux

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

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

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 Hetzner provider
provider "hcloud" {
  token = "${var.hcloud_token}"
}

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

# Create an Ubuntu 18.04 server
resource "hcloud_server" "ubuntu18" {
  name = "ubuntu18"
  image = "ubuntu-18.04"
  server_type = "cx11"
  ssh_keys  = ["${data.hcloud_ssh_key.ssh_key.id}"]
}

# Create Debian 9 server
resource "hcloud_server" "debian9" {
  name = "debian9"
  image = "debian-9"
  server_type = "cx21"
  ssh_keys  = ["${data.hcloud_ssh_key.ssh_key.id}"]
}

# Create CentOS 7 server
resource "hcloud_server" "centos7" {
  name = "centos7"
  image = "centos-7"
  server_type = "cx31"
  ssh_keys  = ["${data.hcloud_ssh_key.ssh_key.id}"]
}

# Output server IPs
output "server_ip_ubuntu18" {
 value = "${hcloud_server.ubuntu18.ipv4_address}"
}
output "server_ip_centos7" {
 value = "${hcloud_server.centos7.ipv4_address}"
}
output "server_ip_debian9" {
 value = "${hcloud_server.debian9.ipv4_address}"
}

Initialize a Terraform working directory:

$ terraform  init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "hcloud" (terraform-providers/hcloud) 1.10.0...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.hcloud: version = "~> 1.10"

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/
└── plugins
    └── linux_amd64
        ├── lock.json
        └── terraform-provider-hcloud_v1.10.0_x4

2 directories, 2 files


To build your Infrastructure with Terraform, run terraform apply.

$ terraform apply

Sample output.

data.hcloud_ssh_key.ssh_key: Refreshing state...

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

Terraform will perform the following actions:

  # hcloud_server.centos7 will be created
  + resource "hcloud_server" "centos7" {
      + backup_window = (known after apply)
      + backups       = false
      + datacenter    = (known after apply)
      + id            = (known after apply)
      + image         = "centos-7"
      + ipv4_address  = (known after apply)
      + ipv6_address  = (known after apply)
      + ipv6_network  = (known after apply)
      + keep_disk     = false
      + location      = (known after apply)
      + name          = "centos7"
      + server_type   = "cx31"
      + ssh_keys      = [
          + "421205",
        ]
      + status        = (known after apply)
    }

  # hcloud_server.debian9 will be created
  + resource "hcloud_server" "debian9" {
      + backup_window = (known after apply)
      + backups       = false
      + datacenter    = (known after apply)
      + id            = (known after apply)
      + image         = "debian-9"
      + ipv4_address  = (known after apply)
      + ipv6_address  = (known after apply)
      + ipv6_network  = (known after apply)
      + keep_disk     = false
      + location      = (known after apply)
      + name          = "debian9"
      + server_type   = "cx21"
      + ssh_keys      = [
          + "421205",
        ]
      + status        = (known after apply)
    }

  # hcloud_server.ubuntu18 will be created
  + resource "hcloud_server" "ubuntu18" {
      + backup_window = (known after apply)
      + backups       = false
      + datacenter    = (known after apply)
      + id            = (known after apply)
      + image         = "ubuntu-18.04"
      + ipv4_address  = (known after apply)
      + ipv6_address  = (known after apply)
      + ipv6_network  = (known after apply)
      + keep_disk     = false
      + location      = (known after apply)
      + name          = "ubuntu16"
      + server_type   = "cx11"
      + ssh_keys      = [
          + "421205",
        ]
      + status        = (known after apply)
    }

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

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

hcloud_server.ubuntu18: Creating...
hcloud_server.centos7: Creating...
hcloud_server.debian9: Creating...
hcloud_server.centos7: Creation complete after 8s [id=2869955]
hcloud_server.debian9: Creation complete after 8s [id=2869956]
hcloud_server.ubuntu18: Creation complete after 8s [id=2869954]

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

Outputs:

server_ip_centos7 = 116.203.44.172
server_ip_debian9 = 116.203.87.93
server_ip_ubuntu18 = 116.203.48.203


Test access to your instances with printed IP addresses.

$ ssh [email protected]
Warning: Permanently added '116.203.44.172' (ECDSA) to the list of known hosts.
[[email protected] ~]# 

$ ssh [email protected]
Warning: Permanently added '116.203.87.93' (ECDSA) to the list of known hosts.
Linux debian9 4.9.0-9-amd64 #1 SMP Debian 4.9.168-1+deb9u3 (2019-06-16) 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]:~# 

$ ssh [email protected]
Warning: Permanently added '116.203.48.203' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 18.04.2 LTS (GNU/Linux 4.15.0-50-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Jun 30 21:25:40 CEST 2019

  System load:  0.65              Processes:           87
  Usage of /:   8.4% of 18.72GB   Users logged in:     0
  Memory usage: 6%                IP address for eth0: 116.203.48.203
  Swap usage:   0%


73 packages can be updated.
40 updates are security updates.
[email protected]:~# 

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.

Similar articles:

How to Provision VMs on KVM with Terraform

Build AWS EC2 Machine Images (AMI) With Packer and Ansible

Become a Linux Geek today by buying our recommended Linux books:

Best Linux Books for Beginners & Experts

Best LPIC-1 and LPIC-2 certification study books