Incrementally Creating A Base System
Technical Background
Infrastructure as Code (IaC) tools like Terraform enable declarative management of cloud resources. Instead of manually provisioning servers via a graphical interface, Terraform allows you to define your infrastructure in version-controlled configuration files. These configurations can be applied repeatedly and modified incrementally, ensuring predictable and reproducible deployments.
For this exercise, the Hetzner Cloud Provider is used to create a Debian-based server. The workflow covers:
- Creating a minimal configuration
- Securing SSH access using firewall rules
- Switching from insecure password-based login to key-based authentication
- Preventing accidental exposure of secrets (API tokens) in version control
- Adding Terraform outputs to display server-specific data (IP address, datacenter)
Note
Hetzner sends an email containing login credentials (IP + root password) if no SSH key is provided when creating a server. This is a fallback security measure. Using SSH keys avoids password-based login entirely and is the recommended best practice.
Solution
Minimal Terraform Setup
- Create a new project directory:
mkdir terraform-hello && cd terraform-hello
- Create
main.tf
containing:
resource "hcloud_server" "web" {
name = "web"
image = "debian-12"
server_type = "cx22"
firewall_ids = [hcloud_firewall.sshFw.id]
ssh_keys = [hcloud_ssh_key.loginUser.id]
}
- Initialize and apply:
terraform init
terraform plan
terraform apply
Note
The console output confirms a successful server creation.
After a successful run, Hetzner sends an email containing the server’s IP address and a randomly generated root password only when no SSH key is provided during server creation.
This ensures the user can still access the server securely after initial provisioning. Once you log in for the first time, you are required to change the root password. When using SSH keys instead, this email is not needed because authentication happens via your private key.
Secure API Tokens
- Create
secrets.auto.tfvars
containing:
api_token_hetzner = "hetzner_api_token"
loginUser_name = "your_email"
- Create
variables.tf
containing:
variable "api_token_hetzner" {
type = string
sensitive = true
}
variable "loginUser_name" {
type = string
sensitive = true
}
- Create
providers.tf
containing:
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
}
}
required_version = ">= 0.13"
}
provider "hcloud" {
token = var.api_token_hetzner
}
- Add
secrets.auto.tfvars
to.gitignore
:
echo "secrets.auto.tfvars" >> .gitignore
Warning
Never commit raw API tokens to version control. Using a separate untracked file is safer.
SSH Key Login and Firewall
- Generate an SSH key (if not existing):
ssh-keygen -t ed25519 -C "your_email@example.com"
- Add SSH key resource in
main.tf
:
resource "hcloud_ssh_key" "loginUser" {
name = var.loginUser_name
public_key = file("~/.ssh/id_ed25519.pub")
}
- Create
network.tf
containing:
resource "hcloud_firewall" "sshFw" {
name = "ssh-firewall"
rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = ["0.0.0.0/0", "::/0"]
}
}
Important
This needs to be added to have SSH access.
- Attach firewall and SSH key to server in
main.tf
:
resource "hcloud_server" "web" {
name = "web"
image = "debian-12"
server_type = "cx22"
firewall_ids = [hcloud_firewall.sshFw.id]
ssh_keys = [hcloud_ssh_key.loginUser.id]
}
- Apply configuration:
terraform apply
- Test SSH login:
ssh root@<your-server_ip>
Success
You can now log in without a password using your private key. The password email from Hetzner is no longer required.
Add Outputs
- Create
outputs.tf
:
output "web_ip_addr" {
value = hcloud_server.web.ipv4_address
description = "The server's IPv4 address"
}
output "web_datacenter" {
value = hcloud_server.web.datacenter
description = "The server's datacenter"
}
- Re-apply configuration:
terraform apply
terraform output
Success
Expected output:
hello_datacenter = "hel1-dc2"
hello_ip_addr = "95.217.154.104"
Info
Outputs make it easier to integrate with scripts and documentation.