Terraform doesn’t just provision infrastructure; it describes it, treating your cloud as a declarative database.
Here’s a DigitalOcean cluster for a stateless web app, managed by Terraform.
# main.tf
provider "digitalocean" {
token = var.do_token
}
resource "digitalocean_droplet" "web_app" {
count = 3
image = "ubuntu-20-04-x64"
name = "web-app-${count.index}"
region = "nyc3"
size = "s-2vcpu-4gb"
ssh_keys = [digitalocean_ssh_key.my_key.id]
monitoring = true
private_networking = true
tags = ["web", "app", "production"]
}
resource "digitalocean_ssh_key" "my_key" {
name = "my-terraform-key"
public_key = file("~/.ssh/id_rsa.pub")
}
resource "digitalocean_loadbalancer" "app_lb" {
name = "app-loadbalancer"
region = "nyc3"
droplet_ids = digitalocean_droplet.web_app[*].id
sticky_sessions = true
algorithm = "round_robin"
healthcheck {
port = 80
protocol = "http"
path = "/"
}
}
output "loadbalancer_ip" {
value = digitalocean_loadbalancer.app_lb.ip
}
variable "do_token" {
description = "DigitalOcean API Token"
type = string
sensitive = true
}
This configuration defines three identical Droplets for our web application, a DigitalOcean SSH key for access, and a load balancer to distribute traffic across them. The output "loadbalancer_ip" will give us the public IP address of the load balancer once deployed.
The core problem Terraform solves is drift. Without it, you SSH into a server, install packages, configure services, and then repeat for the next server. If one server drifts from the others – a package update here, a config change there – your cluster becomes inconsistent and unpredictable. Terraform, by storing your infrastructure’s desired state in code, acts as the single source of truth. When you run terraform apply, it compares your code to the actual state of your infrastructure and makes only the necessary changes to reconcile them.
Internally, Terraform uses providers to interact with APIs. The digitalocean provider knows how to speak to DigitalOcean’s API to create, update, and delete resources like Droplets, firewalls, and load balancers. It maintains a state file (usually terraform.tfstate) that maps the resources defined in your configuration to their real-world counterparts in your DigitalOcean account. This state file is crucial; it’s how Terraform knows what it manages.
The count meta-argument in the digitalocean_droplet resource is a powerful way to create multiple similar resources. Here, count = 3 tells Terraform to create three Droplets. The count.index is a special variable that will be 0 for the first Droplet, 1 for the second, and 2 for the third, allowing us to name them uniquely like web-app-0, web-app-1, etc. The splat expression digitalocean_droplet.web_app[*].id dynamically pulls the IDs of all created Droplets, which are then passed to the load balancer’s droplet_ids argument. This ensures the load balancer always knows about all the web app servers.
When you run terraform init, Terraform downloads the necessary provider plugins. Then, terraform plan shows you exactly what changes Terraform intends to make without actually executing them. Finally, terraform apply executes those changes. If you later change the size of the Droplets to "s-4vcpu-8gb", terraform apply will detect that the existing Droplets are the wrong size and will update them to the new specification.
The healthcheck block within the load balancer configuration is vital for ensuring high availability. It periodically probes the web servers on port 80 using HTTP to the root path (/). If a server fails this health check for a configurable period, the load balancer will temporarily stop sending traffic to it, preventing users from hitting a broken instance. The sticky_sessions = true setting ensures that a user’s requests are consistently routed to the same Droplet for the duration of their session, which can be important for applications that maintain session state on the server.
Most people don’t realize how granularly Terraform can manage updates. If you add a new tag to the digitalocean_droplet resource, like tags = ["web", "app", "production", "new-feature"], Terraform will not recreate the Droplets. Instead, it will perform an in-place update by calling the DigitalOcean API to add that tag to the existing Droplets. This is a significant advantage over manual provisioning or some other IaC tools, as it minimizes downtime and disruption.
The next step in managing this application would be to integrate it with a CI/CD pipeline for automated deployments, or to explore more advanced networking concepts like DigitalOcean’s VPC.