Adım Adım Terraform ile AWS Üzerinde Güvenli Web Sunucusu Mimarisi Kurulumu
Terraform (IaC) kullanarak AWS üzerinde sıfırdan kurumsal standartlarda bir web altyapısı inşa etme rehberi. Özel VPC, subnet, Internet Gateway, güvenlik grupları, dinamik Canonical Ubuntu AMI seçimi ve otomatik Nginx kurulumunun aşamaları; dpkg kilitlerini aşma ve Base64 script optimizasyonları gibi kritik saha pratikleriyle bu yazıda.


Bulut bilişim projelerinde sanal sunucu ve ağ bileşenlerini konsol üzerinden elle butonlara tıklayarak oluşturmak (ClickOps), prototip aşamasında hızlı görünse de üretim ortamlarında takip edilemeyen yapılandırma sapmalarına (configuration drift), unutulan açık portlara ve ciddi güvenlik zafiyetlerine yol açar.
Infrastructure as Code (IaC) prensibi doğrultusunda Terraform kullanılarak; izole bir sanal ağ (VPC), yönlendirme tabloları, güvenlik duvarı kuralları ve üzerinde Nginx çalışan dinamik bir Ubuntu EC2 sunucusunun sıfırdan nasıl ayağa kaldırılacağı, hangi bileşenin neden seçildiği ve sahada karşılaşılan kritik altyapı incelikleri bu rehberde adım adım incelenmektedir.
1. Mimari Tasarım: Hangi Bileşeni Neden Seçtik?
AWS hesabında varsayılan (default) olarak gelen VPC ve subnet yapıları genellikle genel paylaşımlı kurallara sahiptir. Güvenli ve ölçeklenebilir mimarilerin ilk kuralı, her projenin kendi izole ağ katmanında çalışmasıdır.
🏛️ Mimari Şeması
Aşağıdaki diyagram, bu projenin AWS üzerinde kurduğu bileşenleri ve trafik akışını göstermektedir:
📷 Mimari Şema
Temel Bileşenlerin Görevleri ve Mimari Tercih Nedenleri
-
Virtual Private Cloud (VPC -
10.0.0.0/16):- Görevi: AWS üzerinde projeye özel, mantıksal olarak izole edilmiş sanal bir veri merkezidir. 65.536 adet özel IP içeren güvenli bir havuz oluşturur.
- Neden Varsayılan Değil de Özel VPC? AWS'nin hazır VPC'si tüm servislerin birbiriyle konuşabildiği kontrolsüz bir alandır. Özel VPC kurarak CIDR bloklarını, alt ağ bölünmelerini ve dış dünya erişim kurallarını sıfırdan kendi kontrolümüze alırız.
-
Public Subnet (
10.0.1.0/24):- Görevi: VPC havuzundan ayrılan 256 IP'lik alt ağdır.
map_public_ip_on_launch = trueözelliğiyle bu subnet içinde başlatılan sunucular otomatik genel IP alır. - Neden Public Subnet? Amacımız dışarıdan gelen HTTP isteklerine yanıt veren bir web sunucusu çalıştırmaktır. Arka planda çalışacak bir veri tabanı olsaydı onu dışarıya kapalı bir Private Subnet içerisine konumlandırmamız gerekirdi.
- Görevi: VPC havuzundan ayrılan 256 IP'lik alt ağdır.
-
Internet Gateway (IGW) & Route Table:
- Görevi: Subnet içindeki kaynakların dış dünyayla çift yönlü iletişim kurmasını sağlayan sanal kapıdır. Route Table ise
0.0.0.0/0(tüm yerel dışı trafik) hedefini IGW'ye bağlayarak internet çıkış rotasını çizer.
- Görevi: Subnet içindeki kaynakların dış dünyayla çift yönlü iletişim kurmasını sağlayan sanal kapıdır. Route Table ise
-
Security Group (Sanal Güvenlik Duvarı):
- Görevi: EC2 sunucusunun hemen önünde duran durum bilgisi tutan (stateful) güvenlik kalkanıdır. Sadece izin verdiğimiz 80 (HTTP) ve 22 (SSH) portlarına gelen paketleri kabul eder. Giden trafik (egress) açık olduğu için paket güncellemeleri sorunsuz indirilir.
-
Nginx Web Sunucusu:
- Neden Nginx? Doğrudan bir uygulama çalıştırmak yerine Nginx kullanmak; statik dosyaları mikrosaniyeler içinde sunması, yüksek eşzamanlı bağlantı kapasitesi ve gelecekte backend servisleri önüne ters vekil sunucu (reverse proxy) olarak konumlandırılabilmesi açısından endüstri standardıdır.
-
EC2 & User Data (Başlatma Betiği):
- Sunucunun ilk boot anında root yetkisiyle çalışan betiktir (bootstrap script). Paket kilitlerini aşar, Nginx'i kurar ve HTML şablonunu dinamik değişkenlerle besler.
2. Güvenlik ve Kimlik Doğrulama Standartları
Terraform kodlarında AWS API anahtarlarının (AWS_ACCESS_KEY_ID ve AWS_SECRET_ACCESS_KEY) kesinlikle .tf dosyalarına yazılmaması gerekir. Kod versiyon kontrol sistemlerine (Git/GitHub) gönderildiğinde bu anahtarlar botlar tarafından saniyeler içinde taranarak kötüye kullanılabilir.
Güvenli Yetkilendirme (Environment Variables)
Geliştirme ortamında anahtarlar yerel bir .env dosyasında tutulmalı ve oturum değişkeni olarak yüklenmelidir:

Hassas Dosyaların Dışlanması (.gitignore)
Projenin kök dizinine aşağıdaki .gitignore dosyası eklenerek state verilerinin ve gizli anahtarların Git reposuna çıkması engellenmelidir:
gitignore# Kimlik ve çevre değişkenleri .env *.env # Terraform çalışma dizini ve sağlayıcı kütüphaneleri .terraform/ .terraform.lock.hcl # Durum dosyaları (Hassas meta veriler ve IP'ler içerir) *.tfstate *.tfstate.* *.tfvars
3. Modüler Dosya Düzeni
Sorumlulukların ayrılması (Separation of Concerns) ilkesi doğrultusunda altyapı 4 temel dosyaya bölünmüştür:
text├── providers.tf # Sağlayıcı kısıtlamaları ve ortak etiketler (default_tags) ├── variables.tf # Parametrik yapılandırma değişkenleri ├── main.tf # VPC, Subnet, IGW, Route Table, SG ve EC2 tanımları └── outputs.tf # Kurulum sonrası elde edilen IP ve web adresi çıktıları
4. Altyapı Bileşenlerinin Kodlanması
A. Sağlayıcı ve Ortak Etiketler (providers.tf)
AWS sağlayıcısının sürüm aralığı belirlenir ve oluşturulan her kaynağa otomatik işlenecek kurumsal etiketler (default_tags) tanımlanır:
hclterraform { required_version = ">= 1.5.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = var.aws_region # Tüm kaynaklara otomatik olarak atanacak ortak etiketler default_tags { tags = { Project = var.project_name Environment = var.environment ManagedBy = "Terraform" CreatedAt = "2026" } } }
B. Parametrik Değişkenler (variables.tf)
Kodun sert kodlanmış (hardcoded) değerlerden arındırılması, farklı ortamlara (dev, staging, prod) tek hamlede uyarlanabilmesini sağlar:
hclvariable "aws_region" { description = "Kaynakların oluşturulacağı AWS bölgesi." type = string default = "eu-central-1" # Frankfurt } variable "project_name" { description = "Tüm kaynakların isimlendirilmesinde kullanılacak ön ek." type = string default = "terraform-aws-demo" } variable "environment" { description = "Ortam etiketi (dev, staging, prod)." type = string default = "dev" } variable "vpc_cidr" { description = "VPC için CIDR bloğu." type = string default = "10.0.0.0/16" } variable "public_subnet_cidr" { description = "Public Subnet için CIDR bloğu." type = string default = "10.0.1.0/24" } variable "instance_type" { description = "EC2 instance tipi. AWS Free Tier için t2.micro önerilir." type = string default = "t2.micro" } variable "author_name" { description = "Web sayfasında görünecek geliştirici adı." type = string default = "Yusuf" }
C. Altyapı Kaynakları ve Başlatma Betiği (main.tf)
Tüm ağ bileşenleri, dinamik imaj sorgusu ve sunucu ayağa kaldırma kuralları bu ana dosyada toplanır:
hcl# --- 1. VPC --- # Kaynakların çalışacağı izole sanal ağ resource "aws_vpc" "main" { cidr_block = var.vpc_cidr enable_dns_support = true enable_dns_hostnames = true tags = { Name = "${var.project_name}-vpc" } } # --- 2. Internet Gateway --- # VPC'yi dış dünyaya bağlar resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.main.id tags = { Name = "${var.project_name}-igw" } } # --- 3. Public Subnet --- # Sunucunun yerleştirileceği, otomatik public IP dağıtan alt ağ resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = var.public_subnet_cidr availability_zone = "${var.aws_region}a" map_public_ip_on_launch = true tags = { Name = "${var.project_name}-public-subnet" } } # --- 4. Route Table --- # Subnet içindeki bilinmeyen trafiği (0.0.0.0/0) IGW üzerinden yönlendirir resource "aws_route_table" "public_rt" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.igw.id } tags = { Name = "${var.project_name}-public-rt" } } # Route Table'ı subnet ile eşleştirme resource "aws_route_table_association" "public_assoc" { subnet_id = aws_subnet.public.id route_table_id = aws_route_table.public_rt.id } # --- 5. Security Group --- # Güvenlik duvarı: Port 80 (HTTP) ve Port 22 (SSH) erişimine izin verir resource "aws_security_group" "web_sg" { name = "${var.project_name}-web-sg" description = "HTTP ve SSH erisimi" vpc_id = aws_vpc.main.id ingress { description = "HTTP" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # Not: Port 22'nin tüm dünyaya (0.0.0.0/0) açık olması demo amaçlıdır. # Canlı ortamlarda bu kural yalnızca kendi statik IP adresinize kısıtlanmalı # veya doğrudan AWS Systems Manager (SSM) Session Manager kullanılmalıdır. ingress { description = "SSH" from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { description = "Paket güncellemeleri için tüm giden trafiğe izin ver" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "${var.project_name}-sg" } } # --- 6. Dinamik AMI Sorgusu --- # Canonical'ın resmi en güncel Ubuntu 22.04 LTS imajı dinamik olarak çekilir data "aws_ami" "ubuntu" { most_recent = true owners = ["099720109477"] # Canonical resmi yayıncı kimliği filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] } filter { name = "virtualization-type" values = ["hvm"] } } # --- 7. EC2 Web Sunucusu --- resource "aws_instance" "web" { ami = data.aws_ami.ubuntu.id instance_type = var.instance_type subnet_id = aws_subnet.public.id vpc_security_group_ids = [aws_security_group.web_sg.id] user_data_replace_on_change = true user_data = <<-EOF #!/bin/bash # 1. Ubuntu kilit mekanizmasını bekle (unattended-upgrades kilit çözümü) for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do fuser /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || break sleep 6 done # 2. Paket kurulumları export DEBIAN_FRONTEND=noninteractive apt-get update -y apt-get install -y nginx systemctl enable nginx systemctl start nginx # 3. HTML sayfasını Base64 üzerinden hatasız yazdır echo "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9InRyIj4KPGhlYWQ+CiAgPG1ldGEgY2hhcnNldD0iVVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MS4wIj4KICA8dGl0bGU+VGVycmFmb3JtIEFXUyBEZW1vPC90aXRsZT4KICA8bGluayBocmVmPSJodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2NzczI/ZmFtaWx5PUludGVyOndnaHRANDAwOzUwMDs2MDA7NzAwJmZhbWlseT1KZXRCcmFpbnMrTW9ubzp3Z2h0QDQwMDs1MDAmZGlzcGxheT1zd2FwIiByZWw9InN0eWxlc2hlZXQiPgogIDxzdHlsZT4KICAgICogeyBib3gtc2l6aW5nOiBib3JkZXItYm94OyBtYXJnaW46IDA7IHBhZGRpbmc6IDA7IH0KICAgIGJvZHkgewogICAgICBmb250LWZhbWlseTogJ0ludGVyJywgc2Fucy1zZXJpZjsKICAgICAgYmFja2dyb3VuZDogIzBhMGEwYTsKICAgICAgY29sb3I6ICNlNWU1ZTU7CiAgICAgIG1pbi1oZWlnaHQ6IDEwMHZoOwogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKICAgICAgcGFkZGluZzogMzJweCAxNnB4OwogICAgfQogICAgLmNhcmQgewogICAgICB3aWR0aDogMTAwJTsKICAgICAgbWF4LXdpZHRoOiA1NjBweDsKICAgICAgYmFja2dyb3VuZDogIzExMTsKICAgICAgYm9yZGVyOiAxcHggc29saWQgIzIyMjsKICAgICAgYm9yZGVyLXJhZGl1czogMTZweDsKICAgICAgcGFkZGluZzogNDBweDsKICAgIH0KICAgIC5iYWRnZSB7CiAgICAgIGRpc3BsYXk6IGlubGluZS1mbGV4OwogICAgICBhbGlnbi1pdGVtczogY2VudGVyOwogICAgICBnYXA6IDZweDsKICAgICAgZm9udC1zaXplOiAxMnB4OwogICAgICBmb250LXdlaWdodDogNTAwOwogICAgICBjb2xvcjogIzRhZGU4MDsKICAgICAgYmFja2dyb3VuZDogcmdiYSg3NCwyMjIsMTI4LDAuMDgpOwogICAgICBib3JkZXI6IDFweCBzb2xpZCByZ2JhKDc0LDIyMiwxMjgsMC4yKTsKICAgICAgcGFkZGluZzogNHB4IDEycHg7CiAgICAgIGJvcmRlci1yYWRpdXM6IDEwMHB4OwogICAgICBtYXJnaW4tYm90dG9tOiAyNHB4OwogICAgfQogICAgLmRvdCB7CiAgICAgIHdpZHRoOiA2cHg7CiAgICAgIGhlaWdodDogNnB4OwogICAgICBiYWNrZ3JvdW5kOiAjNGFkZTgwOwogICAgICBib3JkZXItcmFkaXVzOiA1MCU7CiAgICB9CiAgICBoMSB7CiAgICAgIGZvbnQtc2l6ZTogMjJweDsKICAgICAgZm9udC13ZWlnaHQ6IDcwMDsKICAgICAgY29sb3I6ICNmZmY7CiAgICAgIG1hcmdpbi1ib3R0b206IDhweDsKICAgIH0KICAgIC5zdWJ0aXRsZSB7CiAgICAgIGZvbnQtc2l6ZTogMTRweDsKICAgICAgY29sb3I6ICM2NjY7CiAgICAgIGxpbmUtaGVpZ2h0OiAxLjY7CiAgICAgIG1hcmdpbi1ib3R0b206IDMycHg7CiAgICAgfQogICAgLmRpdmlkZXIgewogICAgICBib3JkZXI6IG5vbmU7CiAgICAgIGJvcmRlci10b3A6IDFweCBzb2xpZCAjMWUxZTFlOwogICAgICBtYXJnaW4tYm90dG9tOiAyNHB4OwogICAgfQogICAgLmluZm8tbGlzdCB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGZsZXgtZGlyZWN0aW9uOiBjb2x1bW47CiAgICAgIGdhcDogMTRweDsKICAgICAgbWFyZ2luLWJvdHRvbTogMzJweDsKICAgIH0KICAgIC5pbmZvLXJvdyB7CiAgICAgIGRpc3BsYXk6IGZsZXg7CiAgICAgIGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsKICAgICAgYWxpZ24taXRlbXM6IGNlbnRlcjsKICAgIH0KICAgIC5pbmZvLWxhYmVsIHsKICAgICAgZm9udC1zaXplOiAxM3B4OwogICAgICBjb2xvcjogIzU1NTsKICAgIH0KICAgIC5pbmZvLXZhbHVlIHsKICAgICAgZm9udC1mYW1pbHk6ICdKZXRCcmFpbnMgTW9ubycsIG1vbm9zcGFjZTsKICAgICAgZm9udC1zaXplOiAxM3B4OwogICAgICBjb2xvcjogI2NjYzsKICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgIH0KICAgIC5pbmZvLXZhbHVlLmhpZ2hsaWdodCB7CiAgICAgIGNvbG9yOiAjODE4Y2Y4OwogICAgfQogICAgLnRhZ3MgewogICAgICBkaXNwbGF5OiBmbGV4OwogICAgICBnYXA6IDhweDsKICAgICAgZmxleC13cmFwOiB3cmFwOwogICAgfQogICAgLnRhZyB7CiAgICAgIGZvbnQtc2l6ZTogMTJweDsKICAgICAgZm9udC13ZWlnaHQ6IDUwMDsKICAgICAgY29sb3I6ICM1NTU7CiAgICAgIGJhY2tncm91bmQ6ICMxNjE2MTY7CiAgICAgIGJvcmRlcjogMXB4IHNvbGlkICMyMjI7CiAgICAgIHBhZGRpbmc6IDRweCAxMHB4OwogICAgICBib3JkZXItcmFkaXVzOiA2cHg7CiAgICB9CiAgPC9zdHlsZT4KPC9oZWFkPgo8Ym9keT4KICA8ZGl2IGNsYXNzPSJjYXJkIj4KICAgIDxkaXYgY2xhc3M9ImJhZGdlIj48c3BhbiBjbGFzcz0iZG90Ij48L3NwYW4+IFN1bnVjdSBBa3RpZjwvZGl2PgogICAgPGgxPlRlcnJhZm9ybSBpbGUgQVdTIEFsdHlhcGlzaTwvaDE+CiAgICA8cCBjbGFzcz0ic3VidGl0bGUiPkJ1IG9ydGFtIEluZnJhc3RydWN0dXJlIGFzIENvZGUgcHJlbnNpYml5bGUgVGVycmFmb3JtIGt1bGxhbmlsYXJhayBvdG9tYXRpayBvbGFyYWsgb2x1c3R1cnVsbXVzdHVyLjwvcD4KICAgIDxociBjbGFzcz0iZGl2aWRlciI+CiAgICA8ZGl2IGNsYXNzPSJpbmZvLWxpc3QiPgogICAgICA8ZGl2IGNsYXNzPSJpbmZvLXJvdyI+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tbGFiZWwiPkdlbGlzdGlyaWNpPC9zcGFuPgogICAgICAgIDxzcGFuIGNsYXNzPSJpbmZvLXZhbHVlIGhpZ2hsaWdodCI+QVVUSE9SX1ZBTDwvc3Bhbj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImluZm8tcm93Ij4KICAgICAgICA8c3BhbiBjbGFzcz0iaW5mby1sYWJlbCI+QVdTIEJvbGdlc2k8L3NwYW4+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tdmFsdWUiPlJFR0lPTl9WQUw8L3NwYW4+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJpbmZvLXJvdyI+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tbGFiZWwiPkluc3RhbmNlIFRpcGk8L3NwYW4+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tdmFsdWUiPklUWVBFX1ZBTDwvc3Bhbj4KICAgICAgPC9kaXY+CiAgICAgIDxkaXYgY2xhc3M9ImluZm8tcm93Ij4KICAgICAgICA8c3BhbiBjbGFzcz0iaW5mby1sYWJlbCI+T3J0YW08L3NwYW4+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tdmFsdWUiPkVOVl9WQUw8L3NwYW4+CiAgICAgIDwvZGl2PgogICAgICA8ZGl2IGNsYXNzPSJpbmZvLXJvdyI+CiAgICAgICAgPHNwYW4gY2xhc3M9ImluZm8tbGFiZWwiPklQIEFkcmVzaTwvc3Bhbj4KICAgICAgICA8c3BhbiBjbGFzcz0iaW5mby12YWx1ZSIgaWQ9ImlwIj4tPC9zcGFuPgogICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPGhyIGNsYXNzPSJkaXZpZGVyIj4KICAgIDxkaXYgY2xhc3M9InRhZ3MiPgogICAgICA8c3BhbiBjbGFzcz0idGFnIj5UZXJyYWZvcm08L3NwYW4+CiAgICAgIDxzcGFuIGNsYXNzPSJ0YWciPkFXUyBFQzI8L3NwYW4+CiAgICAgIDxzcGFuIGNsYXNzPSJ0YWciPk5naW54PC9zcGFuPgogICAgICA8c3BhbiBjbGFzcz0idGFnIj5VYnVudHUgMjIuMDQ8L3NwYW4+CiAgICAgIDwvZGl2PgogIDwvZGl2PgogIDxzY3JpcHQ+CiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnaXAnKS50ZXh0Q29udGVudCA9IGxvY2F0aW9uLmhvc3RuYW1lOwogIDwvc2NyaXB0Pgo8L2JvZHk+CjwvaHRtbD4K" | base64 -d > /var/www/html/index.html # 4. Terraform değişkenlerini HTML şablonuna dinamik olarak işle sed -i 's|AUTHOR_VAL|${var.author_name}|g' /var/www/html/index.html sed -i 's|REGION_VAL|${var.aws_region}|g' /var/www/html/index.html sed -i 's|ITYPE_VAL|${var.instance_type}|g' /var/www/html/index.html sed -i 's|ENV_VAL|${var.environment}|g' /var/www/html/index.html systemctl restart nginx EOF tags = { Name = "${var.project_name}-web-server" } }
D. Çıktı Değerleri (outputs.tf)
Kurulum tamamlandığında terminale sunucunun genel IP adresini, doğrudan tıklanabilir web bağlantısını ve oluşturulan ağ kimliklerini yazdırmak için çıktılar tanımlanır:
hcloutput "instance_public_ip" { description = "EC2 sunucusunun public IP adresi." value = aws_instance.web.public_ip } output "web_url" { description = "Tarayıcıdan açılabilecek URL." value = "http://${aws_instance.web.public_ip}" } output "vpc_id" { description = "Oluşturulan VPC'nin ID'si." value = aws_vpc.main.id } output "subnet_id" { description = "Sunucunun bulunduğu Subnet ID'si." value = aws_subnet.public.id } output "security_group_id" { description = "Oluşturulan Security Group ID'si." value = aws_security_group.web_sg.id }
5. Sahada Hayat Kurtaran Altyapı İncelikleri
Bu mimariyi inşa ederken kurumsal üretim ortamlarında fark yaratan 3 kritik pratik uygulanmıştır:
1. Ubuntu Paket Kilidi (dpkg lock) Aşma Döngüsü
Ubuntu sunucuları ilk boot anında güvenlik güncellemelerini çekmek için arka planda unattended-upgrades çalıştırır. Başlatma betiği bu işlemi beklemeden apt-get install nginx çalıştırmaya kalkarsa kilit hatası alır ve Nginx kurulamaz. Betik başına eklenen fuser döngüsü kilidin serbest kalmasını bekleyerek kurulumun %100 başarıyla tamamlanmasını garanti eder.
2. Base64 ile Heredoc Çakışmalarını Önleme
Bash heredoc (<<-EOF) bloğu içine CSS veya JavaScript barındıran zengin HTML kodları gömüldüğünde çift tırnaklar ve özel karakterler Terraform derleyicisiyle çakışır. HTML kodunu Base64 formatına çevirip sunucuda base64 -d ile açmak ve dinamik değerleri sed ile enjekte etmek hem kod okunabilirliğini korur hem de sözdizimi hatalarını sıfırlar.
3. Yapılandırma Değişiminde Otomatik Yeniden Kurulum
Normal şartlarda user_data güncellendiğinde Terraform mevcut sunucuyu silmez; bu da yeni başlatma betiğinin çalışmamasına yol açar. user_data_replace_on_change = true direktifi sayesinde başlatma betiğinde bir değişiklik yapıldığında sunucu temiz şekilde baştan ayağa kaldırılır.
6. Dağıtım Döngüsü: Adım Adım Komutlar
Tüm kod dosyaları hazırlandıktan sonra deployment süreci sırasıyla şu 4 adımla işletilir:
1. Çalışma Alanını Hazırlama
bashterraform init
AWS sağlayıcı eklentisi indirilir ve arka plan yapılandırması tamamlanır.
2. Değişiklik Simülasyonu
bashterraform plan

Kod ile AWS üzerindeki gerçek durum kıyaslanır. Eklenecek 7 kaynak (VPC, IGW, Subnet, Route Table, Association, SG, EC2) incelenir.
3. Canlıya Alma (Deployment)
bashterraform apply
Onay verildiğinde ortalama 45 saniye içinde tüm altyapı ayağa kalkar ve terminalde çıktılar listelenir:

Terminalde verilen web_url tarayıcıda açıldığında, Terraform değişkenleriyle dinamik olarak beslenmiş koyu temalı sistem bilgi kartı görüntülenir.
4. Kaynak İmhası (Maliyet Kontrolü)
Test süreci bittiğinde gereksiz bulut maliyetlerinin önüne geçmek için tüm kaynaklar tek komutla kaldırılır:
bashterraform destroy

📷 Sistemin Çalışır Hali
7. GitHub Kaynak Kodları
Bu projede kullanılan tüm Terraform yapılandırma dosyalarına, örnek .env.example şablonuna ve detaylı kurulum adımlarına GitHub Deposu üzerinden ulaşabilirsiniz. Repoyu yıldızlayarak (star) destekleyebilir ve kendi projeleriniz için şablon olarak kullanabilirsiniz.
Sonuç
Terraform ile altyapı inşa etmek; sistemlerin belgelendiği, sürüm kontrolüne alındığı, insan hatasından arındırıldığı ve dakikalar içinde baştan kurulabildiği güvenli bir mühendislik ortamı sunar. İzole bir VPC'den dinamik AMI seçimine ve otomatik paket kurulumuna kadar tüm zincirin kod haline getirilmesi, bulut altyapınızı kurumsal standartlara taşımanın en sağlam adımıdır.