Infrastructure as Code untuk Semua Orang
Learn by Doing
Terraform adalah tools Infrastructure as Code (IaC) dari HashiCorp yang memungkinkan kamu mendefinisikan infrastruktur dalam file konfigurasi.
| Tool | Language | Cloud |
|---|---|---|
| Terraform | HCL (HashiCorp Configuration Language) | Multi-cloud (AWS, GCP, Azure, Cloudflare, dll) |
| AWS CloudFormation | JSON/YAML | AWS only |
| Pulumi | TypeScript, Python, Go, C# | Multi-cloud |
| Ansible | YAML | Config management + IaC |
Terraform berjalan sebagai binary tunggal, tidak perlu instalasi kompleks.
# Download binary
wget https://releases.hashicorp.com/terraform/1.10.0/terraform_1.10.0_linux_amd64.zip
# Extract
unzip terraform_1.10.0_linux_amd64.zip
# Move ke PATH
sudo mv terraform /usr/local/bin/
# Verify
terraform version
# Download release signatures
wget https://releases.hashicorp.com/terraform/1.10.0/terraform_1.10.0_SHA256SUMS
wget https://releases.hashicorp.com/terraform/1.10.0/terraform_1.10.0_SHA256SUMS.sig
# Import HashiCorp GPG key
gpg --recv-keys 51852D87348FFC4C
# Verify signature
gpg --verify terraform_1.10.0_SHA256SUMS.sig terraform_1.10.0_SHA256SUMS
# Check checksum
sha256sum -c terraform_1.10.0_SHA256SUMS 2>&1 | grep terraform
tfenv atau tenv untuk manage multiple Terraform versions
Providers adalah plugin yang menghubungkan Terraform ke layanan/infrastruktur target.
Provider memiliki resource types yang bisa di-manage. Contoh: aws_instance, cloudflare_zone, digitalocean_droplet
# AWS Provider
provider "aws" {
region = "ap-southeast-1"
access_key = "AKIA..." # atau via environment/AWS_PROFILE
secret_key = "..."
}
# Cloudflare Provider
provider "cloudflare" {
api_token = "your-api-token"
}
# DigitalOcean Provider
provider "digitalocean" {
token = "your-token"
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
Resource adalah block paling penting di Terraform. Represents infrastructure object.
resource "type" "name" {
# configuration
}
resource "aws_s3_bucket" "website" {
bucket = "my-website-assets-2024"
tags = {
Name = "My Website Assets"
Environment = "production"
ManagedBy = "Terraform"
}
}
resource "aws_s3_bucket_website_configuration" "website" {
bucket = aws_s3_bucket.website.id
index_document {
suffix = "index.html"
}
error_document {
key = "error.html"
}
}
# Format: resource_type.resource_name.attribute
aws_s3_bucket.website.bucket
aws_s3_bucket.website.id
aws_s3_bucket" "assets" bukan "bucket"
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # explicit dependency
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
}
resource "aws_instance" "server" {
count = 3
ami = "ami-12345678"
instance_type = "t3.micro"
tags = {
Name = "Server ${count.index}"
}
}
# Referencing: aws_instance.server[0], aws_instance.server[1], dll
variable "users" {
type = map(object({
email = string
role = string
}))
default = {
alice = { email = "[email protected]", role = "admin" }
bob = { email = "[email protected]", role = "developer" }
}
}
resource "aws_iam_user" "users" {
for_each = var.users
name = each.key
tags = {
Email = each.value.email
Role = each.value.role
}
}
for_each daripada count jika value tidak ordinal (tidak berbasis index)
Variables membuat konfigurasi reusable dan fleksibel.
variable "region" {
type = string
description = "AWS region"
default = "ap-southeast-1"
}
variable "instance_count" {
type = number
description = "Jumlah instance"
default = 3
}
variable "enable_monitoring" {
type = bool
description = "Aktifkan CloudWatch monitoring"
default = true
}
variable "availability_zones" {
type = list(string)
default = ["ap-southeast-1a", "ap-southeast-1b"]
}
variable "tags" {
type = map(string)
default = {
Environment = "production"
ManagedBy = "Terraform"
}
}
variable "environment" {
type = string
default = "production"
validation {
condition = contains(["dev", "staging", "production"], var.environment)
error_message = "Environment harus dev, staging, atau production."
}
}
# terraform.tfvars (auto-loaded)
region = "ap-southeast-1"
environment = "production"
# atau manual: terraform apply -var-file="production.tfvars"
Output menampilkan nilai setelah apply, berguna untuk integrasi dengan tools lain.
output "name" {
description = "Deskripsi output"
value = expression
sensitive = true/false
}
output "bucket_name" {
description = "Nama S3 bucket"
value = aws_s3_bucket.website.bucket
}
output "bucket_arn" {
description = "ARN S3 bucket"
value = aws_s3_bucket.website.arn
}
output "instance_ips" {
description = "IP addresses dari semua instance"
value = [for inst in aws_instance.server : inst.private_ip]
}
output "db_password" {
description = "Database password"
value = aws_db_instance.db.password
sensitive = true # tidak akan ditampilkan di terminal
}
# Setelah apply
terraform output
terraform output bucket_name
terraform output -raw bucket_name # raw string tanpa quotes
# Di file lain (remote state)
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "my-terraform-state"
key = "network/terraform.tfstate"
}
}
# Gunakan output
subnet_id = data.terraform_remote_state.network.outputs.subnet_id
State adalah file yang menyimpan mapping antara konfigurasi dan real infrastructure.
# Local State (default, disimpan di terraform.tfstate)
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
# Remote State dengan S3 + DynamoDB (Production)
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "prod/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
# DynamoDB table untuk state locking
resource "aws_dynamodb_table" "terraform_locks" {
name = "terraform-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
Modules adalah container untuk multiple resources yang digunakan bersama.
modules/
└── networking/
├── main.tf # resources
├── variables.tf # input variables
├── outputs.tf # output values
└── versions.tf # provider versions
module "vpc" {
source = "./modules/networking"
name = "my-vpc"
cidr_block = "10.0.0.0/16"
enable_nat = true
}
# Referencing module outputs
module.vpc.private_subnet_ids
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0.0"
name = "my-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-southeast-1a", "ap-southeast-1b"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
public_subnets = ["10.0.11.0/24", "10.0.12.0/24"]
}
Contoh lengkap: Deploy static site dengan Caddy server + Cloudflare DNS.
static-site/
├── main.tf
├── variables.tf
├── outputs.tf
├── .gitignore
└── README.md
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
# Zone/Domain configuration
resource "cloudflare_zone" "main" {
zone = var.domain
}
# DNS A Record - point ke server
resource "cloudflare_record" "www" {
zone_id = cloudflare_zone.main.id
name = "www"
value = var.server_ip
type = "A"
proxied = true # Cloudflare proxy (CDN + protection)
}
# DNS A Record - root domain
resource "cloudflare_record" "root" {
zone_id = cloudflare_zone.main.id
name = "@"
value = var.server_ip
type = "A"
proxied = true
}
# Caddy Server configuration (untuk provisioning server)
resource "null_resource" "caddy_setup" {
connection {
type = "ssh"
user = "ubuntu"
host = var.server_ip
private_key = var.ssh_private_key
agent = false
}
provisioner "remote-exec" {
inline = [
"sudo apt-get update && sudo apt-get install -y caddy",
"sudo caddy adapt --config /etc/caddy/Caddyfile",
"sudo systemctl restart caddy"
]
}
}
# Generate Caddyfile
resource "local_file" "caddyfile" {
filename = "${path.module}/Caddyfile"
content = <<-EOT
${var.domain} {
root * /var/www/html
file_server
encode gzip
}
www.${var.domain} {
redirect https://${var.domain}
}
EOT
}
variable "cloudflare_api_token" {
type = string
description = "Cloudflare API Token"
sensitive = true
}
variable "domain" {
type = string
description = "Domain name"
default = "example.com"
}
variable "server_ip" {
type = string
description = "Server IP address"
}
variable "ssh_private_key" {
type = string
description = "SSH private key for server access"
sensitive = true
}
Workspaces memungkinkan multiple infrastructure states dalam satu configuration.
# List workspaces
terraform workspace list
# Create workspace
terraform workspace new production
# Switch workspace
terraform workspace select production
# Show current workspace
terraform workspace show
# Delete workspace (must be not current)
terraform workspace delete dev
# Deteksi environment dari workspace
locals {
environment = terraform.workspace
is_prod = terraform.workspace == "production"
}
# Gunakan untuk conditional resources
resource "aws_instance" "server" {
count = local.is_prod ? 3 : 1
ami = var.ami_id
instance_type = local.is_prod ? "t3.large" : "t3.micro"
tags = {
Name = "server-${count.index}-${local.environment}"
Workspace = local.environment
}
}
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "${terraform.workspace}/terraform.tfstate"
region = "ap-southeast-1"
dynamodb_table = "terraform-locks"
}
}
Terraform bisa import infrastruktur yang sudah ada agar bisa di-manage via code.
# Sintaks dasar
terraform import [options] ADDRESS ID
# Contoh: Import existing S3 bucket
terraform import aws_s3_bucket.my_bucket my-bucket-name
# Import dengan config yang sudah ada
terraform import module.vpc.aws_vpc.main vpc-12345678
import {
id = "i-1234567890abcdef0"
to = aws_instance.web
}
import {
id = "prod-web-server"
to = aws_instance.web
}
# Dengan provider-specific ID
import {
id = "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0"
to = aws_instance.web
}
terraform importterraform plan untuk lihat state differences# Generate config dari existing resource (Terraform 1.6+)
terraform plan -generate-config-out=generated.tf
infra/
├── environments/
│ ├── dev/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── .terraform.lock.hcl
│ └── prod/
│ ├── main.tf
│ ├── variables.tf
│ └── .terraform.lock.hcl
├── modules/
│ ├── networking/
│ ├── compute/
│ └── database/
└── global/
└── s3-state-backend/
# Terraform
.terraform/
.terraform.lock.hcl
*.tfstate
*.tfstate.*
crash.log
crash.*.log
# Override
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Local .terraform directories
**/.terraform/*
# .tfvars files (kalau ada secrets)
*.tfvars
*.tfvars.json
# IDE
.vscode/
.idea/
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # Lock major version
}
}
}
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "ap-southeast-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
sensitive = true untuk variable/output yang sensitif*.tfvars dengan credentials# Initialize (download providers, setup backend)
terraform init
# Format code (jaga konsistensi)
terraform fmt
# Validate syntax (tanpa akses provider)
terraform validate
# Preview changes
terraform plan
terraform plan -var-file="prod.tfvars"
# Apply changes
terraform apply
terraform apply -var-file="prod.tfvars"
terraform apply -auto-approve # skip approval (hati-hati!)
# Destroy infrastructure
terraform destroy
terraform destroy -target=aws_instance.server # destroy spesifik
# State management
terraform state list
terraform state mv aws_instance.old aws_instance.new # rename
terraform state rm aws_instance.deleted # remove dari state
terraform show
# Output
terraform output
terraform output -raw variable_name
| File | Fungsi |
|---|---|
main.tf |
Resource definitions |
variables.tf |
Input variable definitions |
outputs.tf |
Output value definitions |
providers.tf |
Provider configuration |
versions.tf |
Terraform & provider version constraints |
terraform.tfvars |
Variable values (auto-loaded) |
Apa format file Terraform?
Command apa untuk apply perubahan?
Apa fungsi Terraform state?