Terraform on DigitalOcean: The Complete Beginner’s Guide (2026)
DigitalOcean’s simple API and flat pricing make it the best cloud provider for learning Terraform. In this guide, you’ll go from zero to deploying a production-ready infrastructure — Droplets, Kubernetes clusters, managed databases, and networking — all defined in code.
Disclosure: Some links in this article are affiliate links. If you purchase through these links, we may earn a small commission at no extra cost to you.
Prerequisites:
– A DigitalOcean account (sign up with $200 free credit)
– Terraform installed (brew install terraform or download)
– A DigitalOcean API token (Settings → API → Generate New Token)
Project Setup
Create a new directory and initialize Terraform:
mkdir do-infrastructure && cd do-infrastructure
Create providers.tf:
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
}
variable "do_token" {
description = "DigitalOcean API token"
type = string
sensitive = true
}
provider "digitalocean" {
token = var.do_token
}
Create terraform.tfvars (add to .gitignore):
do_token = "your-api-token-here"
Initialize the project:
terraform init
Deploying Your First Droplet
Create droplets.tf:
# Look up the latest Ubuntu image
data "digitalocean_images" "ubuntu" {
filter {
key = "distribution"
values = ["Ubuntu"]
}
filter {
key = "status"
values = ["available"]
}
sort {
key = "created"
direction = "desc"
}
}
# Create an SSH key
resource "digitalocean_ssh_key" "default" {
name = "terraform-key"
public_key = file("~/.ssh/id_rsa.pub")
}
# Create a Droplet
resource "digitalocean_droplet" "web" {
image = "ubuntu-24-04-x64"
name = "web-server"
region = "nyc1"
size = "s-1vcpu-2gb"
ssh_keys = [digitalocean_ssh_key.default.fingerprint]
tags = ["web", "production"]
}
output "web_ip" {
value = digitalocean_droplet.web.ipv4_address
}
Deploy it:
terraform plan # Preview changes
terraform apply # Create resources
Networking: VPC, Firewall, Load Balancer
VPC (Virtual Private Cloud)
resource "digitalocean_vpc" "production" {
name = "production-vpc"
region = "nyc1"
ip_range = "10.10.10.0/24"
}
# Update droplet to use VPC
resource "digitalocean_droplet" "web" {
image = "ubuntu-24-04-x64"
name = "web-server"
region = "nyc1"
size = "s-1vcpu-2gb"
vpc_uuid = digitalocean_vpc.production.id
ssh_keys = [digitalocean_ssh_key.default.fingerprint]
}
Firewall
resource "digitalocean_firewall" "web" {
name = "web-firewall"
droplet_ids = [digitalocean_droplet.web.id]
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["your.ip.address/32"]
}
inbound_rule {
protocol = "tcp"
port_range = "80"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "tcp"
port_range = "443"
source_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "all"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "udp"
port_range = "all"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
}
Load Balancer
resource "digitalocean_loadbalancer" "web" {
name = "web-lb"
region = "nyc1"
vpc_uuid = digitalocean_vpc.production.id
forwarding_rule {
entry_port = 80
entry_protocol = "http"
target_port = 80
target_protocol = "http"
}
healthcheck {
port = 80
protocol = "http"
path = "/"
}
droplet_ids = [digitalocean_droplet.web.id]
}
output "lb_ip" {
value = digitalocean_loadbalancer.web.ip
}
Managed Kubernetes Cluster
This is where DigitalOcean really shines — managed Kubernetes at a fraction of AWS/GCP prices.
Create kubernetes.tf:
resource "digitalocean_kubernetes_cluster" "production" {
name = "production-cluster"
region = "nyc1"
version = "1.31.1-do.3"
vpc_uuid = digitalocean_vpc.production.id
node_pool {
name = "default-pool"
size = "s-2vcpu-4gb"
auto_scale = true
min_nodes = 2
max_nodes = 5
tags = ["production"]
}
}
# Save kubeconfig for kubectl access
resource "local_file" "kubeconfig" {
content = digitalocean_kubernetes_cluster.production.kube_config[0].raw_config
filename = "${path.module}/kubeconfig.yaml"
}
output "cluster_endpoint" {
value = digitalocean_kubernetes_cluster.production.endpoint
}
Managed Database
resource "digitalocean_database_cluster" "postgres" {
name = "app-database"
engine = "pg"
version = "16"
size = "db-s-1vcpu-1gb"
region = "nyc1"
node_count = 1
private_network_uuid = digitalocean_vpc.production.id
}
# Restrict database access to your Kubernetes cluster
resource "digitalocean_database_firewall" "postgres" {
cluster_id = digitalocean_database_cluster.postgres.id
rule {
type = "k8s"
value = digitalocean_kubernetes_cluster.production.id
}
}
output "db_connection" {
value = digitalocean_database_cluster.postgres.uri
sensitive = true
}
Spaces (Object Storage)
resource "digitalocean_spaces_bucket" "assets" {
name = "myapp-assets-bucket"
region = "nyc3"
acl = "private"
cors_rule {
allowed_headers = ["*"]
allowed_methods = ["GET"]
allowed_origins = ["https://myapp.com"]
max_age_seconds = 3600
}
lifecycle_rule {
enabled = true
expiration {
days = 90
}
prefix = "tmp/"
}
}
resource "digitalocean_cdn" "assets" {
origin = digitalocean_spaces_bucket.assets.bucket_domain_name
}
Managing State
For team collaboration, store Terraform state remotely using DigitalOcean Spaces:
terraform {
backend "s3" {
endpoint = "nyc3.digitaloceanspaces.com"
bucket = "terraform-state-bucket"
key = "production/terraform.tfstate"
region = "us-east-1" # Required but unused
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
skip_s3_checksum = true
}
}
Complete Production Stack
Here’s everything together — what a small production infrastructure looks like:
├── providers.tf # Provider config + variables
├── vpc.tf # VPC and networking
├── kubernetes.tf # DOKS cluster
├── database.tf # Managed PostgreSQL
├── storage.tf # Spaces + CDN
├── firewall.tf # Network security
├── outputs.tf # Useful outputs
├── terraform.tfvars # Secrets (gitignored)
└── terraform.tfstate # State (remote in production)
Monthly cost estimate for this stack:
| Resource | Size | Monthly Cost |
|---|---|---|
| Kubernetes (2 nodes) | s-2vcpu-4gb | $48 |
| PostgreSQL | db-s-1vcpu-1gb | $15 |
| Load Balancer | Standard | $12 |
| Spaces (25GB) | Standard | $5 |
| Total | ~$80/month |
Compare that to AWS (EKS + RDS + ALB + S3): easily $200+/month for equivalent resources.
Tips and Best Practices
-
Use workspaces for multi-environment management:
bash
terraform workspace new staging
terraform workspace new production -
Use modules for reusable components:
hcl
module "web_cluster" {
source = "./modules/kubernetes"
region = "nyc1"
min_nodes = var.environment == "production" ? 3 : 1
} -
Tag everything for cost tracking and organization
-
Use
terraform planin CI to preview changes before applying -
Destroy dev environments when not in use:
bash
terraform workspace select dev
terraform destroy -auto-approve
Recommended Terraform Resources
Terraform has a steep learning curve. These resources will accelerate your progress beyond this guide:
- Terraform: Up & Running by Yevgeniy Brikman — covers modules, state management, and team workflows in depth. The best single Terraform book available.
- Infrastructure as Code by Kief Morris — broader IaC patterns and practices that apply to any tool, not just Terraform.
- Kubernetes Up & Running — essential companion if you are provisioning DOKS clusters with Terraform.
Next Steps
- Sign up for DigitalOcean ($200 free credit) if you haven’t already
- Follow this guide step by step
- Add ArgoCD to your cluster for GitOps deployments — see our CI/CD tools comparison
- Set up monitoring with Prometheus and Grafana
Ready to start? Get $200 free credit on DigitalOcean and build your entire infrastructure as code.
Related: Best Cloud Hosting for Kubernetes in 2026 | How to Build a DevOps Home Lab
