Terraform lets you define your cloud infrastructure (servers, networks, databases, DNS, firewalls) as code, with versioning, review and reproducibility. Clicking through a cloud console in production often ends in mistakes; with Terraform you can recreate the same cluster in ten different regions in seconds.
Basic Structure
# main.tf
terraform {
required_version = ">= 1.6"
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45"
}
}
backend "s3" {
bucket = "my-tf-state"
key = "prod/terraform.tfstate"
region = "eu-central-1"
}
}
provider "hcloud" {
token = var.hcloud_token
}
Essential Commands
terraform init # download providers, set up backend
terraform fmt -recursive
terraform validate
terraform plan # WHAT will change? — read this before applying
terraform apply # apply (re-shows plan, asks for confirmation)
terraform destroy # delete everything (be careful!)
terraform state list
terraform state show hcloud_server.web
terraform output
Variables
# variables.tf
variable "hcloud_token" {
type = string
sensitive = true
}
variable "server_count" {
type = number
default = 2
validation {
condition = var.server_count > 0 && var.server_count <= 10
error_message = "Must be between 1 and 10."
}
}
variable "region" {
type = string
default = "nbg1"
}
# terraform.tfvars
hcloud_token = "your-token"
server_count = 3
Hetzner Example: Web + DB
resource "hcloud_ssh_key" "admin" {
name = "admin"
public_key = file("~/.ssh/id_ed25519.pub")
}
resource "hcloud_network" "internal" {
name = "internal"
ip_range = "10.0.0.0/16"
}
resource "hcloud_network_subnet" "main" {
network_id = hcloud_network.internal.id
type = "cloud"
network_zone = "eu-central"
ip_range = "10.0.1.0/24"
}
resource "hcloud_server" "web" {
count = var.server_count
name = "web-${count.index + 1}"
image = "ubuntu-24.04"
server_type = "cx22"
location = var.region
ssh_keys = [hcloud_ssh_key.admin.id]
network {
network_id = hcloud_network.internal.id
ip = "10.0.1.${10 + count.index}"
}
user_data = file("cloud-init.yaml")
}
resource "hcloud_load_balancer" "web_lb" {
name = "web-lb"
load_balancer_type = "lb11"
location = var.region
}
Modules
Factor repeated infrastructure into modules — write once, reuse across environments.
# modules/webapp/main.tf
variable "name" {}
variable "count" { default = 1 }
resource "hcloud_server" "this" {
count = var.count
name = "${var.name}-${count.index + 1}"
image = "ubuntu-24.04"
server_type = "cx22"
}
output "ips" {
value = hcloud_server.this[*].ipv4_address
}
# main.tf
module "frontend" {
source = "./modules/webapp"
name = "frontend"
count = 3
}
module "backend" {
source = "./modules/webapp"
name = "backend"
count = 2
}
State Management
Terraform stores the current state of your infrastructure in terraform.tfstate. On a team, remote state is mandatory — otherwise two concurrent applies will corrupt the state.
# S3 + DynamoDB lock
terraform {
backend "s3" {
bucket = "my-tf-state"
key = "prod/terraform.tfstate"
region = "eu-central-1"
dynamodb_table = "tf-state-locks" # state lock
encrypt = true
}
}
# Alternatives: Terraform Cloud (HashiCorp), GCS, Azure Blob
Workspaces
terraform workspace new staging
terraform workspace new production
terraform workspace select production
terraform workspace list
# Workspace-specific tfvars
terraform apply -var-file="prod.tfvars"
Drift Detection
# Did someone change infra manually?
terraform plan -detailed-exitcode
# exit 0: no changes
# exit 2: the plan includes changes
# Run this regularly in CI → alert
Importing Existing Resources
# Resource created by hand in the UI, bring it under Terraform:
terraform import hcloud_server.web 12345678
# Terraform 1.5+ — import blocks
import {
to = hcloud_server.web
id = "12345678"
}
Terraform + CI/CD
# .github/workflows/terraform.yml
name: Terraform
on:
pull_request:
paths: ['terraform/**']
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
working-directory: terraform
- run: terraform fmt -check -recursive
- run: terraform validate
- run: terraform plan -out=plan.bin
env:
TF_VAR_hcloud_token: ${{ secrets.HCLOUD_TOKEN }}
Conclusion
Terraform is the written contract of modern cloud infrastructure. Infrastructure as code, PR review, versioning, reproducibility — all way beyond a single YAML file. It feels slow the first time; but cloning your infrastructure in five minutes on the second project becomes addictive.
Reach out to KEYDAL for Terraform infrastructure design and migration on AWS, Hetzner and Azure. Contact us