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.
The output will be printed to the screen. Save it in a safe place for later use.
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 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.geeksforgeeks.org"
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.geeksforgeeks.org"
+ 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 root@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.
root@debian11:~#
Other examples using different templates
Ubuntu 22.04:
# Create Ubuntu server
resource "hcloud_server" "ubuntu" {
name = "ubuntu.geeksforgeeks.org"
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.geeksforgeeks.org"
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.geeksforgeeks.org"
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.geeksforgeeks.org"
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.geeksforgeeks.org"
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:
root@server:~# ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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.
Terraform video courses in udemy:
- HashiCorp Certified: Terraform Associate
- Learn DevOps: Infrastructure Automation With Terraform
- HashiCorp Certified: Terraform Associate -50 Practical Demos
- Terraform on AWS with SRE & IaC DevOps | Real-World 20 Demos
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: