Terraform'u tek başınıza kullanırken durum dosyası görünmez bir ayrıntıdır: dizinde terraform.tfstate diye bir şey oluşur, siz de ona hiç dokunmazsınız. İkinci bir kişi projeye katıldığı ya da işi bir CI hattına taşıdığınız gün ise Terraform state yönetimi aniden en kritik konu hâline gelir. Çünkü o dosya, Terraform'un "hangi kaynak benim" bilgisini tuttuğu tek yerdir; kaybolursa, bozulursa ya da iki kişi aynı anda yazarsa altyapınızın kontrolünü kaybedersiniz.
Bu rehberde durum dosyasının gerçekte ne tuttuğunu, yerel dosyanın neden yetersiz kaldığını, uzak arka uç (backend) kurulumunu, kilitleme mekanizmasını, state komut ailesinin doğru kullanımını, sapma (drift) kavramını ve ortamları ayırmanın iki yöntemini ele alacağım. Terraform'un temel kavramlarına yabancıysanız önce Terraform nedir yazısını okumanız bu rehberi çok daha anlaşılır kılar.
Durum Dosyası Tam Olarak Ne Tutar#
Terraform bildirimsel bir araçtır: yapılandırmanızı okur, gerçek dünyanın hâlini öğrenir ve aradaki farkı kapatır. "Gerçek dünyanın hâli" bilgisini her seferinde sıfırdan keşfetmek pratikte imkânsız olduğu için, Terraform yönettiği her kaynağın kimliğini bir eşleme tablosunda saklar. İşte durum dosyası budur: yapılandırmadaki docker_container.web adı ile gerçek dünyadaki kaynak kimliği arasındaki bağ.
Dosya JSON biçimindedir ve içinde kaynakların tüm özniteliklerinin son bilinen değerleri bulunur. Bir bakış atalım:
# Durumdaki kaynakları listele
terraform state list
# docker_container.web
# docker_image.nginx
# random_password.db
# Tek bir kaynağın tüm özniteliklerini gör
terraform show -json | jq '.values.root_module.resources[0]'
# Okunabilir biçimde
terraform state show docker_container.web
⚠️ Burada anlaşılması gereken en önemli şey şudur: durum dosyası bir sır deposudur. Terraform, kaynakların dönen tüm özniteliklerini olduğu gibi saklar; üretilen parolalar, bağlantı dizeleri, özel anahtarlar hepsi düz metin olarak orada durur. Yapılandırmada sensitive = true yazmak yalnızca ekrana basılmasını engeller, dosyaya yazılmasını değil. Dolayısıyla durum dosyasına erişim, altyapınızın sırlarına erişimdir.
| Durum dosyasında bulunan | Neden orada | Risk |
|---|---|---|
| Kaynak kimlikleri | Eşleme için zorunlu | Düşük |
| Tüm öznitelik değerleri | Fark hesaplamak için | Orta |
| Üretilen parolalar, anahtarlar | Sağlayıcı döndürdüğü için | Yüksek |
Çıktı (output) değerleri | Modüller arası paylaşım | Yüksek |
| Serial ve lineage | Sürüm ve köken takibi | Düşük |
Aynı riski Ansible tarafında Vault ile çözüyorsanız mantık burada da benzerdir; şifreleme ve erişim kısıtlaması olmadan sırların düz metin durması kabul edilemez. Sır yönetimi yaklaşımını Ansible Vault ile gizli veri yönetimi yazısında ayrıntılı ele almıştım.
Yerel State'in Sınırları ve Uzak Backend#
Varsayılan olarak durum, çalıştığınız dizinde terraform.tfstate olarak tutulur. Bu, tek kişilik deneme projeleri dışında üç ciddi sorunla gelir. Birincisi paylaşım: dosya sizin diskinizde olduğu için ekipteki diğer kişi Terraform'u çalıştırdığında hiçbir kaynağı görmez ve hepsini yeniden yaratmaya kalkar. İkincisi kayıp riski: disk bozulursa ya da dizini silerseniz Terraform tüm kaynaklarını unutur. Üçüncüsü eşzamanlılık: iki kişi aynı anda apply çalıştırırsa dosya bozulur.
Çözüm uzak arka uçtur (backend): durum dosyası nesne depolamada ya da bir durum servisinde tutulur, herkes oradan okur ve oraya yazar. Nesne depolama tabanlı, S3 uyumlu bir arka uç yapılandırması şöyle görünür:
# backend.tf
terraform {
backend "s3" {
bucket = "firmaniz-terraform-state"
key = "uretim/altyapi.tfstate"
region = "eu-central-1"
# Sunucu tarafı şifreleme
encrypt = true
# Kilitleme için (sağlayıcıya göre değişir)
# dynamodb_table = "terraform-locks"
}
}
Arka uç yapılandırmasında değişken kullanılamaz; var.x yazamazsınız, çünkü bu blok Terraform'un geri kalanından önce okunur. Ortam bazlı ayarları komut satırından geçirirsiniz:
# Kısmi yapılandırma: değerleri init sırasında ver
terraform init \
-backend-config="bucket=firmaniz-terraform-state" \
-backend-config="key=test/altyapi.tfstate"
# Ya da bir dosyadan
terraform init -backend-config=ortamlar/uretim.backend.hcl
Yerel durumdan uzak arka uca geçiş sancısız bir işlemdir; init mevcut durumu taşımayı teklif eder:
terraform init -migrate-state
# Do you want to copy existing state to the new backend?
# Pre-existing state was found while migrating the previous "local" backend
# Enter a value: yes
Taşımadan önce yerel dosyanın bir kopyasını alın. Geçiş sırasında bir aksilik olursa elinizde tek kurtarma noktası o kopya olur:
cp terraform.tfstate terraform.tfstate.$(date +%F).yedek
Arka uç seçerken üç özelliği arayın: sürümleme (eski hâllere dönebilme), şifreleme (sırlar için) ve kilitleme desteği. Sürümleme açık bir nesne deposu, bozulan bir durumu geri almanın en pratik yoludur. Genel yedekleme stratejinizle bunu bütünleştirmek için yedekleme çözümlerimize göz atabilirsiniz.
Durum Kilitleme ve Eşzamanlı Çalışma#
Terraform, bir apply sırasında durum üzerinde kilit alır. Kilidi destekleyen bir arka uç kullanıyorsanız, ikinci kişi aynı anda çalıştırdığında şu mesajı görür:
Error: Error acquiring the state lock
Lock Info:
ID: 4f2a9e1c-8b3d-47a2-9f10-5c6d7e8a9b01
Path: firmaniz-terraform-state/uretim/altyapi.tfstate
Operation: OperationTypeApply
Who: ahmet@is-istasyonu
Created: 2026-08-25 09:14:22 UTC
Bu bir hata değil, koruma mekanizmasıdır: iki eşzamanlı yazma durumu bozar. Doğru tepki beklemektir. Ancak gerçekten bir çalıştırma yarıda kesildiyse (ağ koptu, süreç öldü) kilit ortada kalabilir. O zaman ve yalnızca o zaman kilidi elle açarsınız:
# Önce: gerçekten kimse çalıştırmıyor mu, doğrulayın
terraform force-unlock 4f2a9e1c-8b3d-47a2-9f10-5c6d7e8a9b01
⚠️ force-unlock komutunu, başka biri gerçekten çalışırken kullanmak durum bozulmasının en hızlı yoludur. Kilit bilgisindeki Who ve Created alanlarına bakın; kilit sahibiyle konuşmadan bu komutu çalıştırmayın.
CI hatlarında kilit zaman aşımını yapılandırmak da işe yarar; kısa bir bekleme, iki işin sırayla çalışmasını sağlar:
terraform apply -lock-timeout=5m
| Senaryo | Ne yapmalı |
|---|---|
| Kilit var, meslektaşınız çalışıyor | Bekleyin veya -lock-timeout verin |
| Kilit var, süreç çöktü | Who alanını doğrulayın, force-unlock |
| Kilitleme desteklenmiyor | Arka ucu kilitleyen bir tipe taşıyın |
| CI'da iki iş aynı anda | Hat düzeyinde eşzamanlılık sınırı koyun |
Ekip içinde çalışma disiplini de en az teknik kilit kadar önemlidir: değişiklikleri pull request üzerinden yürütmek, plan çıktısını incelemeye eklemek ve apply yetkisini tek bir kanala (örneğin ana dala birleştirme) bağlamak. Dal düzeni konusunda Git branch stratejileri yazısı bu akışı kurmanıza yardımcı olur.
State Komut Ailesi: list, show, mv, rm, import#
Durum dosyasını elle düzenlemeyin; JSON'u açıp değiştirmek serial ve iç tutarlılığı bozar. Terraform bunun yerine bir komut ailesi sunar.
# Durumdaki tüm kaynakları listele
terraform state list
# Belirli bir kaynağın öznitelikleri
terraform state show docker_container.web
# Kaynağı yeniden adlandır (kaynak silinmez, sadece adres değişir)
terraform state mv docker_container.web docker_container.web_uretim
# Bir kaynağı Terraform'un yönetiminden ÇIKAR (gerçek kaynak silinmez)
terraform state rm docker_container.eski
# Var olan bir kaynağı Terraform'a bağla
terraform import docker_container.web abc123def456
# Durumun tamamını dosyaya al
terraform state pull > durum-yedek.json
Bu komutların hangisinin ne yaptığını karıştırmak pahalıya patlar; ikisini özellikle ayırt edin:
| Komut | Duruma etkisi | Gerçek kaynağa etkisi |
|---|---|---|
state mv | Adres değişir | Yok |
state rm | Kayıt silinir | Yok (kaynak yaşamaya devam eder) |
destroy | Kayıt silinir | Kaynak yok edilir |
import | Kayıt eklenir | Yok |
state rm en çok "artık bu kaynağı Terraform yönetmesin ama silmesin" durumunda kullanılır; ardından yapılandırmadan da kaldırmanız gerekir, yoksa bir sonraki plan onu yeniden yaratmayı önerir.
import ise elle kurulmuş bir altyapıyı Terraform'a taşımanın yoludur. Modern sürümlerde bunu komut yerine yapılandırmada bir blokla da yapabilirsiniz, ki bu yöntem gözden geçirilebilir olduğu için tercih edilir:
import {
to = docker_container.web
id = "abc123def456"
}
İçe aktarma akışı şudur ve sırası önemlidir:
- Kaynağın Terraform karşılığını koda yazın (henüz
applyyok). importbloğunu ekleyin ya daterraform importkomutunu çalıştırın.terraform plançalıştırın.- Plan çıktısı boş olana kadar kodu gerçeğe göre düzeltin.
- Ancak o zaman
applyçalıştırın.
Dördüncü adımı atlamak, içe aktardığınız kaynağın hemen değiştirilmesine ya da yeniden yaratılmasına yol açar. Plan boş değilse, kodunuz gerçek kaynakla uyuşmuyor demektir.
Sapma (Drift), Refresh ve Elle Değişiklikler#
Sapma, gerçek altyapının durum dosyasında yazandan farklılaşmasıdır. Sebebi genellikle insandır: biri panelden bir ayar değiştirmiş, bir başkası acil müdahalede SSH ile bir kural eklemiştir. Terraform bunu plan sırasında yaptığı eşitleme (refresh) adımında fark eder.
# Sadece sapmayı gör, değişiklik önerme
terraform plan -refresh-only
# Sapmayı durum dosyasına kabul et (gerçeği doğru say)
terraform apply -refresh-only
# Eşitlemeyi hiç yapma (hızlı ama gerçeği görmezsin)
terraform plan -refresh=false
Sapmayla karşılaştığınızda iki seçeneğiniz vardır ve hangisini seçtiğiniz bilinçli bir karar olmalıdır. Ya elle yapılan değişikliği kaybedersiniz (normal apply ile kod ne diyorsa ona döndürür), ya da değişikliği koda taşırsınız ve kaynak kodun tekrar tek doğru kaynak olmasını sağlarsınız. İkincisi neredeyse her zaman doğru cevaptır; birincisi ise değişikliğin gerçekten yanlış olduğu durumlarda geçerlidir.
Bir kaynağı bilerek yeniden yaratmak isterseniz (örneğin bozuk bir konteyner), taint yerine artık replace bayrağı kullanılır:
# Bir sonraki apply'da bu kaynağı yok et ve yeniden yarat
terraform apply -replace="docker_container.web"
Sapmayı azaltmanın en etkili yolu tekniktir değil örgütseldir: üretim ortamında panel ve SSH erişimini okuma ile sınırlamak, değişikliğin tek yolunu kod yapmak. Bunu tam uygulayamıyorsanız en azından düzenli olarak plan -refresh-only çalıştırıp raporlayın; haftada bir gördüğünüz sapma, altı ay birikmiş sapmadan çok daha kolay kapanır.
Ortam Ayrımı: Workspace mi Ayrı Dizin mi#
Aynı kodu test ve üretim için kullanmak isteyeceksiniz. İki yöntem vardır ve aralarındaki fark, kaza yapma olasılığınızdır.
Çalışma alanı (workspace) yaklaşımı, aynı kod ve aynı arka uçla ayrı durum dosyaları tutar:
terraform workspace new test
terraform workspace new uretim
terraform workspace list
# default
# test
# * uretim
terraform workspace select test
terraform apply
Kod içinde hangi alanda olduğunuzu terraform.workspace ile okuyabilirsiniz. Kurulumu kolaydır ama tehlikelidir: hangi alanda olduğunuzu unutup üretimde destroy çalıştırmak yalnızca bir komut uzaklıktadır.
Ayrı dizin yaklaşımı, her ortama kendi dizinini ve kendi arka uç anahtarını verir:
altyapi/
├── modules/
│ └── web/ # ortak tanım
├── test/
│ ├── main.tf # module "web" { source = "../modules/web" ... }
│ └── backend.hcl
└── uretim/
├── main.tf
└── backend.hcl
| Ölçüt | Workspace | Ayrı dizin |
|---|---|---|
| Kurulum kolaylığı | Yüksek | Orta |
| Yanlış ortamda çalışma riski | Yüksek | Düşük |
| Ortamlar arası fark yönetimi | Koşullarla, karmaşıklaşır | Doğal |
| Farklı arka uç/hesap kullanımı | Zor | Kolay |
| Erişim yetkisini ayırma | Zor | Kolay |
Üretim ortamı söz konusu olduğunda ayrı dizin yaklaşımını öneririm. Workspace, aynı ortamın kısa ömürlü kopyalarını (özellik dalı başına bir test ortamı gibi) yönetmek için daha uygundur.
Sık Yapılan Hatalar ve Tuzaklar#
Durum dosyasını Git'e commit etmek. İçindeki sırlar ifşa olur, iki kişinin dosyası çakışır ve birleştirme çatışmasını çözmeye çalışmak durumu tamamen bozar. .gitignore dosyanıza *.tfstate ve *.tfstate.* satırlarını ilk gün ekleyin.
JSON'u elle düzenlemek. Bir kaydı elle silmek serial sayacını ve iç tutarlılığı bozar; Terraform bir daha o dosyayı doğru okuyamayabilir. Ne yapmak isterseniz isteyin, karşılığı olan bir state alt komutu vardır.
state rm ile destroy karıştırmak. state rm kaynağı Terraform'un defterinden siler ama gerçek kaynak yaşamaya devam eder ve artık kimse onu yönetmez; faturası gelmeye devam eder, güncellenmez, unutulur. destroy ise gerçekten yok eder. İkisini karıştırmak ya hayalet kaynaklar ya da veri kaybı üretir.
Kilitlemesiz arka uç kullanmak. Yalnızca dosya saklayan, kilit desteği olmayan bir arka uç, tek kişilik projeler dışında er ya da geç bozulmuş bir duruma yol açar. Kilit desteği olan bir seçenek kullanın.
Yedek almadan geçiş yapmak. Arka uç değiştirmeden, büyük bir yeniden yapılandırmadan ya da state mv serisinden önce terraform state pull > yedek.json çalıştırın. Bu tek satır, geri dönülmez bir hatayı beş dakikalık bir düzeltmeye çevirir.
Sırların düz metin durduğunu unutmak. Durum dosyasını okuyabilen herkes veritabanı parolanızı da okuyabilir. Nesne deposu kovasını herkese kapalı, şifreli ve erişim kaydı tutulur biçimde yapılandırın.
Sıkça Sorulan Sorular#
Terraform state dosyasını Git'e koyabilir miyim#
Hayır, koymamalısınız. Dosya içinde üretilen parolalar, bağlantı dizeleri ve kaynak kimlikleri düz metin olarak bulunur; depoya erişimi olan herkes bunları okur. Ayrıca iki kişi aynı anda çalıştığında Git birleştirme çatışması oluşur ve bir durum dosyasını elle birleştirmek onu bozmakla eşdeğerdir. Doğru çözüm, sürümleme ve kilitleme destekleyen bir uzak arka uç kullanmaktır.
State dosyamı kaybettim, ne yapmalıyım#
Öncelikle arka ucunuzda sürümleme açıksa bir önceki sürümü geri yükleyin; bu, en hızlı ve en güvenli çözümdür. Yerel yedeğiniz varsa (terraform.tfstate.backup veya sizin aldığınız kopya) onu kullanabilirsiniz. Hiçbir yedek yoksa tek yol, mevcut tüm kaynakları tek tek import ile yeniden duruma almaktır; bu büyük bir altyapıda günler sürebilir. Bu yüzden yedekleme ve sürümleme, arka uç seçiminde birinci kriter olmalıdır.
terraform state rm kaynağı gerçekten siler mi#
Hayır. state rm yalnızca Terraform'un kaydını siler; gerçek kaynak olduğu yerde çalışmaya devam eder, sadece artık Terraform tarafından yönetilmez. Gerçekten silmek istiyorsanız terraform destroy ya da hedefli bir yok etme kullanmalısınız. Bu ayrım kritiktir: state rm sonrası yapılandırmadan da kaldırmazsanız, bir sonraki plan aynı kaynağı ikinci kez yaratmayı önerir ve elinizde iki kopya kalır.
Uzak backend ücretli mi#
Arka ucun kendisi Terraform'un ücretsiz bir özelliğidir; maliyet yalnızca durumu sakladığınız yerden kaynaklanır. Bir nesne depolama kovasında birkaç yüz kilobaytlık bir dosya tutmanın maliyeti ihmal edilebilir düzeydedir. HashiCorp'un barındırdığı bulut hizmetinin küçük ekipler için ücretsiz bir seviyesi vardır; kendi altyapınızda S3 uyumlu bir depolama kullanarak da hiçbir ek ücret ödemeden ekipçe çalışabilirsiniz.
İki kişi aynı anda apply çalıştırırsa ne olur#
Kilitleme destekleyen bir arka uç kullanıyorsanız ikinci kişi "state lock" hatası alır ve işlem başlamaz; bu, istenen davranıştır. Kilitleme yoksa iki süreç aynı dosyayı yazar ve sonuçta kaynakların bir kısmı duruma hiç kaydedilmez. Bu noktadan sonra Terraform gerçek dünyayla defterini uyuşturamaz, bazı kaynakları ikinci kez yaratmaya kalkar. Kilitleme bu yüzden isteğe bağlı bir konfor değil, temel gerekliliktir.
Sapmayı nasıl tespit ederim#
terraform plan -refresh-only komutu, yapılandırmaya göre değişiklik önermeden yalnızca gerçek dünya ile durum dosyası arasındaki farkları gösterir. Bunu düzenli olarak, örneğin gecelik bir işte çalıştırıp çıktıyı ekibe raporlamak iyi bir uygulamadır. Fark gördüğünüzde kararınızı bilinçli verin: ya elle yapılan değişikliği koda taşıyın ya da normal bir apply ile kodun tanımladığı hâle geri döndürün.
Ortamları workspace ile mi ayırmalıyım#
Kısa ömürlü ve birbirinin aynısı olan ortamlar için workspace pratiktir. Ancak test ve üretim gibi kalıcı, farklı yetkilere ve farklı ayarlara sahip ortamlar için ayrı dizin ve ayrı arka uç anahtarı kullanmak daha güvenlidir. Workspace'in en büyük riski, hangi alanda olduğunuzu unutup üretimde beklemediğiniz bir komut çalıştırmanızdır; ayrı dizin yaklaşımında bu kaza yapısal olarak zorlaşır.
Kapanış#
Durum dosyası, Terraform'un hafızasıdır ve ona nasıl davrandığınız altyapınızın ne kadar güvenilir olduğunu doğrudan belirler. Aklınızda kalması gereken dört alışkanlık şunlar: durumu asla Git'e koymayın, sürümleme ve kilitleme destekleyen bir uzak arka uçta tutun; dosyayı elle düzenlemek yerine state alt komutlarını kullanın; her riskli işlemden önce terraform state pull ile yedek alın; ve sapmayı düzenli olarak -refresh-only ile ölçüp elle yapılan değişiklikleri koda geri taşıyın. Bir de state rm ile destroy arasındaki farkı asla karıştırmayın.
Durum dosyanızı ve altyapınızı barındıracağınız zemin için bulut sunucu, VDS ve sanal sunucu paketlerimize göz atabilirsiniz. Durum dosyası dahil kritik verilerinizin düzenli kopyalarını almak için yedekleme çözümlerimiz, kurulum ve işletme yükünü devretmek isterseniz sunucu yönetimi hizmetimiz uygun bir başlangıç noktasıdır. Diğer altyapı otomasyonu rehberleri için wiki bölümüne bakabilirsiniz.