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
Related guides: What is DNS, settings · Domain names & WHOIS lookup · Hosting types guide · Nginx configuration · Plesk panel guide
# 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
terraform.tfstate can contain secrets as plain text. Never commit it to Git, and keep your backend encrypted.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 }}
Modern Web Hosting and Server Infrastructure
A performant web hosting service rests on three infrastructure decisions: NVMe SSD disks (4-6× IOPS over SATA SSD), LiteSpeed Web Server or Nginx + LSCache (9× request capacity over Apache) and CloudLinux + Imunify360 isolation. The hosting provider's control panel (cPanel, Plesk, DirectAdmin), daily backup policy, data center location and support response time make a big difference too. Turkish locations give low latency to local visitors, while Hetzner Frankfurt or OVH Roubaix suit global traffic. As your site grows, transitioning from shared hosting to VPS to dedicated server scales CPU/RAM/disk to your needs.
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