Mert Özen Her Satırda Daha İleri
DevOps Infrastructure as Code: Terraform — #07

Environment'ları Ayırmak: Workspace mi, Dizin mi, tfvars mı? Blast Radius ve Klasör Düzeni

Mert Özen 14 Kas 2026 15 dk 17 görüntülenme
Environment'ları Ayırmak: Workspace mi, Dizin mi, tfvars mı? Blast Radius ve Klasör Düzeni

Her environment'ın kendi state'i olmalı; uzun ömürlü dev, staging ve prod için en güvenli yol, ortak kodu bir module'e koyup her ortamı kendi backend'i ve değerleriyle ayrı bir klasörden çağırmak. Aynı state'e staging değerleri vermenin dev'i nasıl replace ettiğini, CLI workspace'lerini, tfvars ve backend config yolunu ve dizin düzenini gerçek çıktılarla deniyor, state'i katmanlara bölmeyi, terraform_remote_state'in risklerini ve const variable ile module sürümü terfisini görüyoruz.

Sürüm notu: Bu yazıdaki örnekler Terraform 1.17.2, kreuzwerker/docker provider 4.7.0, hashicorp/http 3.6.3, pg backend için PostgreSQL 18 ve Docker Engine 29 ile denenmiştir. AzureRM 5.14.0 kodu için init ve validate çalıştırıldı; Azure'a plan/apply yapılmadı.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) dev ortamı ayakta, state'i PostgreSQL backend'inde. Ekip bir staging ortamı istiyor. En kısa yol gibi görünen şeyi yapıyorsun: aynı klasörde bir staging.tfvars yazıp terraform plan -var-file=staging.tfvars çalıştırıyorsun. 3. yazının ★★☆ alıştırması tam olarak buydu. Plan staging için yeni kaynaklar mı açar, yoksa dev'e mi dokunur? Volume'lerdeki prevent_destroy bu sefer ne yapar?

Aynı State'e Başka Ortamın Değerleri

Denediğim staging.tfvars:

environment = "staging"

api = {
  image_tag     = "1.1.0"
  external_port = 18481
}

Plan çıktısı:

$ terraform plan -var-file=staging.tfvars
...
  # docker_network.bookstore must be replaced
-/+ resource "docker_network" "bookstore" {
...
      ~ name         = "tfseries-dev" -> "tfseries-staging" # forces replacement
...
  # module.api.docker_volume.data["covers"] must be replaced
...
      ~ name        = "tfseries-dev-covers" -> "tfseries-staging-covers" # forces replacement
...
Plan: 3 to add, 0 to change, 3 to destroy.
...
Error: Instance cannot be destroyed

  on ../modules/bookstore-service/main.tf line 17:
  17: resource "docker_volume" "data" {

Resource module.api.docker_volume.data["covers"] has
lifecycle.prevent_destroy set, but the plan calls for this resource to be
destroyed.

Terraform staging için yeni bir şey açmıyor; dev'in kaynaklarını staging'e çevirmeye çalışıyor. Sebebi basit: state bu klasörde tek. State'te docker_network.bookstore adresinde bir network var, adı tfseries-dev. Kod aynı adres için artık tfseries-staging diyor, Docker'da network adı değişemediği için replace gerekiyor. Volume'leri prevent_destroy kurtardı, plan hata verip durdu. Koruması olmayan bir kaynak olsaydı (Azure tarafında web app ya da storage container gibi) plan sorunsuz biter, apply edilince dev silinip yerine staging kurulurdu.

Buradan çıkan kural, bu yazının geri kalanının temeli: her ortamın kendi state'i olmalı. Terraform kodu bir ortamı değil, bir ortamın nasıl görüneceğini tarif eder; hangi ortamı yönettiğini belirleyen şey state'tir. Ortamlar için ayrı state elde etmenin üç yaygın yolu var.

Yol 1: CLI Workspace'leri

Terraform CLI'ın workspace'leri, aynı klasör ve aynı backend içinde birden fazla state tutmanı sağlar. Her klasör default adlı bir workspace ile başlar:

$ terraform workspace new staging
Created and switched to workspace "staging"!

You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.
$ terraform workspace list
  default
* staging

$ terraform plan -var-file=staging.tfvars
...
  # docker_network.bookstore will be created
  # module.api.docker_container.this will be created
  # module.api.docker_volume.data["covers"] will be created
  # module.api.docker_volume.data["exports"] will be created
  # module.api.terraform_data.image will be created
Plan: 5 to add, 0 to change, 0 to destroy.

Bu sefer plan dev'e dokunmuyor, beş yeni kaynak açıyor. Backend aynı kaldığı için iki state de aynı PostgreSQL tablosunda, workspace adıyla ayrılmış satırlarda duruyor:

$ docker exec tfseries-state psql -U terraform -d terraform_state \
    -c "select id, name, length(data) as bytes from bookstore_dev.states order by id"
 id |  name   | bytes
----+---------+-------
  1 | default | 15834
  2 | staging |   181
(2 rows)

Azure Storage backend'inde de durum aynı: default dışındaki workspace'ler aynı container'da, key'in sonuna env:<workspace> eklenmiş ayrı blob'lar olarak durur. Workspace'in avantajı hız: tek klasör, tek init, provider ve module cache'i bir kez iner.

Sorun, hangi workspace'te olduğunu yalnız CLI'ın aklında tutması. Kodda ortam farkı yaratmak için terraform.workspace kullanılır ve bu da kolayca şuna dönüşür:

locals {
  environment = terraform.workspace == "default" ? "dev" : terraform.workspace
  sku_name    = terraform.workspace == "prod" ? "P0v3" : (terraform.workspace == "staging" ? "B2" : "B1")
  replication = terraform.workspace == "prod" ? "ZRS" : "LRS"
}

Birisi production için workspace açarken prod yerine prd yazarsa:

$ terraform workspace new prd
$ terraform console
> [local.environment, local.sku_name, local.replication]
[
  "prd",
  "B1",
  "LRS",
]

Hiçbir hata yok. Production, dev boyutlarında ve zone yedeklemesi olmadan kurulur. Daha önemlisi, bütün workspace'ler aynı backend'i, yani aynı kimlik bilgilerini ve aynı erişim hakkını paylaşır: dev'e apply yetkisi olan herkes prod state'ini de okuyup yazabilir. HashiCorp'un dokümanı da ayrı kimlik bilgisi ve erişim kontrolü gereken ortamlar için workspace'leri önermiyor; önerdiği kullanım, bir değişikliği denemek için geçici, paralel bir kopya açmak. Bookstore'da da workspace'i feature branch'leri için kısa ömürlü test ortamlarına bırakıyorum.

HCP (HashiCorp Cloud Platform; HashiCorp'un yönetilen bulut platformu) Terraform'daki workspace'ler aynı adı taşısa da farklı bir şey: orada her workspace'in kendi variable'ları, state'i ve yetkileri var; daha çok aşağıdaki "ortam başına dizin" modeline benziyor.

Yol 2: Tek Kod, Ortam Başına tfvars ve Backend Config

İkinci yolda kod tek klasörde kalır, her ortamın bir .tfvars dosyası ve bir backend config dosyası olur. Backend block'u kodda boş bırakılır (4. yazıdaki partial configuration), ortama özgü kısım init'e verilir:

# backend/dev.pg.tfbackend
schema_name = "bookstore_dev"

# backend/staging.pg.tfbackend
schema_name = "bookstore_staging"
$ terraform init -backend-config=backend/dev.pg.tfbackend
$ terraform plan -var-file=dev.tfvars
...
Plan: 0 to add, 0 to change, 0 to destroy.

$ terraform init -reconfigure -backend-config=backend/staging.pg.tfbackend
$ terraform plan -var-file=staging.tfvars
...
Plan: 5 to add, 0 to change, 0 to destroy.

$ terraform plan -var-file=dev.tfvars
...
  # docker_network.bookstore will be created
  # module.api.docker_container.this will be created
...
Plan: 5 to add, 0 to change, 0 to destroy.

İlk iki adım beklendiği gibi: dev'in state'inde değişiklik yok, staging'in boş state'inde beş kaynak oluşacak. Üçüncü adım bu yöntemin zayıf noktası. Klasör en son hangi backend ile init edildiyse onu hatırlıyor (.terraform/terraform.tfstate içinde); tfvars ise her komutta ayrıca veriliyor. Staging backend'i bağlıyken dev'in değerleriyle plan aldım ve Terraform, staging'in state'ine dev adlı kaynaklar yazmaya hazırlandı. Apply edilseydi dev ile aynı adı taşıyan network ve container'lar çakışırdı; Azure'da ise aynı ada sahip ikinci bir kaynak denemesi ya hata verir ya da daha kötüsü, iki state'in aynı kaynağı yönetmesine yol açar.

Bu yöntem CI'da iyi çalışır, çünkü pipeline her job'da init ve plan'i aynı ortam adıyla birlikte çalıştırır. Laptop'ta iki ortam arasında gidip gelen biri için ise "backend bir yerde, değerler başka yerde" ayrımı hata kaynağı.

Yol 3: Ortam Başına Dizin ve Ortak Module

Üçüncü yolda her ortam ayrı bir root module'dür. Ortak kod bir module'e taşınır; ortam klasöründe yalnız backend, provider ve o ortamın değerleriyle module çağrısı kalır. Yerel örneğin yeni düzeni: bookstore tek bir ortamı (network, API module'ü, check) tarif eden module, envs altındaki her klasör de bir ortamın root'u.

infra/
├── bootstrap-local/
├── modules/
│   └── bookstore-service/
└── local/
    ├── bookstore/
    └── envs/
        ├── dev/
        └── staging/

Eski infra/local'deki dosyalar (locals, network, API module çağrısı, check block'u) olduğu gibi infra/local/bookstore'a taşındı; yalnız provider block'u çıktı, çünkü 6. yazıda gördüğümüz gibi provider configuration root'ta durur. dev ortamının root'u:

terraform {
  required_version = "~> 1.17"

  # Bağlantı bilgisi PG_CONN_STR environment variable'ından gelir
  backend "pg" {
    schema_name = "bookstore_dev"
  }

  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 4.7"
    }
    http = {
      source  = "hashicorp/http"
      version = "~> 3.6"
    }
  }
}
provider "docker" {}

module "bookstore" {
  source = "../../bookstore"

  environment = "dev"
  api = {
    image_tag     = "1.1.0"
    external_port = 18480
    app_settings = {
      Logging__LogLevel__Default = "Information"
    }
  }
  payments_api_key = var.payments_api_key
}

Backend'deki schema adı ve ortamın değerleri artık kodda, aynı klasörde. Ayrı bir tfvars dosyası yok; dev'i değiştirmek isteyen envs/dev'i değiştiren bir pull request açar ve review eden kişi hangi ortama dokunulduğunu dosya yolundan görür. staging klasörü aynı şekilde, yalnız schema bookstore_staging, port 18481 ve log ayarı yok.

dev'in state'i zaten var ve içindeki adresler root'a göre yazılmış. Kaynakları yeniden yaratmamak için 6. yazıdaki moved block'larını bu kez bir üst seviyede kullanıyoruz:

# Bu ortamın kaynakları önceden root module'deydi (infra/local)
moved {
  from = docker_network.bookstore
  to   = module.bookstore.docker_network.bookstore
}

moved {
  from = module.api
  to   = module.bookstore.module.api
}

İkinci block'a dikkat: module.api'nin tamamını tek block'la taşıyor, içindeki dört kaynak için ayrı ayrı yazmaya gerek yok. Yeni klasörde init ve plan:

$ cd infra/local/envs/dev
$ export PG_CONN_STR=$(terraform -chdir=../../../bootstrap-local output -raw pg_conn_str)
$ terraform init
...
- bookstore in ../../bookstore
- bookstore.api in ../../../modules/bookstore-service
...
$ terraform plan
...
  # docker_network.bookstore has moved to module.bookstore.docker_network.bookstore
  # module.api.docker_container.this has moved to module.bookstore.module.api.docker_container.this
  # module.api.docker_volume.data["covers"] has moved to module.bookstore.module.api.docker_volume.data["covers"]
  # module.api.docker_volume.data["exports"] has moved to module.bookstore.module.api.docker_volume.data["exports"]
  # module.api.terraform_data.image has moved to module.bookstore.module.api.terraform_data.image
Plan: 0 to add, 0 to change, 0 to destroy.

Aynı schema'ya bağlandığımız için init var olan state'i buldu, plan da yalnız adres değişikliği gösteriyor. Apply'dan sonra container yerinde, health endpoint'i 200 dönüyor. staging klasörü ise kendi boş state'iyle başlıyor ve dev'e hiç dokunmuyor:

$ cd ../staging
$ terraform init
$ terraform plan
...
  # module.bookstore.docker_network.bookstore will be created
  # module.bookstore.module.api.docker_container.this will be created
  # module.bookstore.module.api.docker_volume.data["covers"] will be created
  # module.bookstore.module.api.docker_volume.data["exports"] will be created
  # module.bookstore.module.api.terraform_data.image will be created
Plan: 5 to add, 0 to change, 0 to destroy.

Bu düzenin bedeli, ortam sayısı kadar init ve ortam klasörleri arasında birkaç satırlık tekrar. Kazancı ise her ortamın ayrı state'i, ayrı backend ayarı ve gerekirse ayrı kimlik bilgisi; hangi ortamda çalıştığın da dosya yolundan belli. HashiCorp'un workspace dokümanı da ayrı erişim kontrolü gereken durumlar için bu modeli öneriyor: ortak kısım module'de, her ortam yalnız backend ve birkaç module çağrısından oluşan ayrı bir configuration.

Azure Tarafı ve Üç Yolun Karşılaştırması

Azure kodu da aynı düzene geçti: bookstore module'ü bir ortamın resource group, storage, SQL ve web app kaynaklarını kuruyor, envs altında dev, staging ve prod klasörleri var.

infra/azure/
├── bookstore/
└── envs/
    ├── dev/
    ├── staging/
    └── prod/
terraform {
  required_version = "~> 1.17"

  backend "azurerm" {
    resource_group_name  = "rg-bookstore-tfstate-weu"
    storage_account_name = "stbookstoretfstateweu"
    container_name       = "tfstate"
    key                  = "bookstore/prod.tfstate"
    use_azuread_auth     = true
  }

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 5.14"
    }
    http = {
      source  = "hashicorp/http"
      version = "~> 3.6"
    }
  }
}
module "bookstore" {
  source = "../../bookstore"

  environment = "prod"
  api = {
    image_name = "bookstore/bookstore-api"
    image_tag  = "1.1.0"
    sku_name   = "P0v3"
  }
  shared_key_vault = {
    name                = "kv-platform-prod-weu"
    resource_group_name = "rg-platform-prod-weu"
  }
  sql_admin_password = var.sql_admin_password
}

4. yazıdaki partial backend dosyası (backend/dev.azurerm.tfbackend) artık gerekmiyor; her ortamın state key'i kendi klasöründe yazılı. Production değerleri de kodda görünür: P0v3 plan, IP kısıtı yok, Key Vault kv-platform-prod-weu. 6. yazıda root'taki api variable'ına koyduğumuz "prod'da B1 (App Service'in en küçük Basic katmanı) olmaz" validation'ı module'le birlikte taşındı; prod klasöründe sku_name'i silince validate yine geçiyor; bu kural plan sırasında değerlendiriliyor. Bunu Azure'a gitmeden, 10. yazının konusu olan mock_provider'lı bir terraform test ile denedim:

$ terraform test
tests/prod.tftest.hcl... in progress
  run "plan"... fail

Error: Invalid value for variable

  on main.tf line 5, in module "bookstore":
   5:   api = {
   6:     image_name = "bookstore/bookstore-api"
   7:     image_tag  = "1.1.0"
   8:   }
    ├────────────────
    │ var.api.sku_name is "B1"
    │ var.environment is "prod"

Production needs at least a P0v3 plan, not B1.

dev klasöründe, eski root'taki altı adresi module.bookstore altına taşıyan bir moved.tf var. Üç klasörde de init -backend=false ve validate geçti; Azure'a plan almadım.

ÖlçütCLI workspacetfvars + backend configOrtam başına dizin
State ayrımıAynı backend, ayrı satır/blobAyrı backend ayarıAyrı backend ayarı
Kimlik ve erişim ayrımıYok, herkes her ortamı görürMümkünMümkün
Hangi ortamdayım?terraform workspace showSon init ve verilen tfvarsKlasör yolu
Ortam farkı nerede?terraform.workspace koşullarıtfvars dosyalarıOrtam klasöründeki module çağrısı
En uygun kullanımKısa ömürlü test kopyalarıHer şeyi CI'ın çalıştırdığı ekiplerKalıcı dev/staging/prod

State'i Katmanlara Bölmek: Blast Radius

Ortamları ayırmak bir eksen. İkinci eksen, tek bir ortamın içindeki kaynakları da birden fazla state'e bölmek. Amaç blast radius'u (bir hatanın etkileyebileceği alanın büyüklüğü) küçültmek: web app'in bir ayarını değiştiren apply'ın, veritabanını silebilecek durumda olmaması. Azure'da yaygın bir ayrım şöyle:

  • Platform/network katmanı: VNet, subnet, DNS zone, ortak Key Vault. Seyrek değişir, çoğu zaman platform ekibinindir.
  • Data katmanı: Azure SQL, BookstoreDb, storage account. Uzun ömürlüdür, kaybı geri dönüşsüzdür.
  • App katmanı: App Service Plan, Web App, managed identity. Her deploy'da değişebilir.

Her katmanın kendi state'i olunca, app katmanında yanlış giden bir plan en fazla app katmanını bozar; data katmanının state'inde olmayan bir veritabanına dokunamaz. Bunun bedeli, katmanlar arasında veri taşıma ihtiyacı: app katmanı SQL Server'ın adını, ortak Key Vault'un ID'sini bilmek zorunda. Üç yol var.

Birincisini zaten kullanıyoruz: data source. Paylaşılan Docker network'ünü ve Azure'daki ortak Key Vault'u adlarıyla okuyoruz. Naming convention belli olduğu sürece en gevşek bağ bu; okuyan taraf, kaynağı hangi araçla kimin kurduğunu bilmek zorunda değil.

İkincisi, terraform_remote_state ile başka bir state'in output'larını okumak. Ayrı bir klasörde dev'in output'larını okudum:

data "terraform_remote_state" "dev" {
  backend = "pg"

  config = {
    conn_str    = var.pg_conn_str
    schema_name = "bookstore_dev"
  }
}

output "dev_api_url" {
  value = data.terraform_remote_state.dev.outputs.api_url
}
$ terraform apply
...
Outputs:

dev_api_url = "http://localhost:18480/health"

$ grep -o '"conn_str"[^,]*' terraform.tfstate
"conn_str": "postgres://terraform:<password>@localhost:15480/terraform_state?sslmode=disable"
...

Çalışıyor, ama iki şeye dikkat. Dokümanın da uyardığı gibi, data source yalnız output'ları gösterse de okuyanın bütün state'e erişimi olması gerekir; bizim örnekte aynı bağlantı bilgisiyle dev'in state'indeki ödeme API key'i de okunabilir. İkincisi, denemede fark ettiğim bir ayrıntı: data source'un config'i, içindeki bağlantı bilgisi ve şifresiyle birlikte okuyan tarafın kendi state'ine yazıldı. Variable'ı sensitive yapmak bunu engellemedi; şifreyi çıktıda maskeledim.

Üçüncüsü, bir katmanın paylaşmak istediği değerleri ayrı bir yere yazması: Key Vault secret'ı, Azure App Configuration ya da kaynakların üzerindeki tag'ler. Okuyan taraf yalnız o değerlere erişir, state'e değil. Bookstore için sıram şu: önce naming convention ile data source, gerekirse yayınlanmış değerler; terraform_remote_state'i yalnız aynı ekibin, aynı erişim seviyesindeki katmanları arasında kullanmak.

Bu yazıda Bookstore'u katmanlara ayırmadım: kaynak sayısı az ve platform katmanı zaten başka bir ekibin. Data katmanını ayırma zamanı, veritabanı ile web app'in farklı kişiler tarafından farklı sıklıkta değiştirilmeye başladığı an.

Module Sürümünü Ortamlar Arasında Taşımak: const Variable

Ortak module'ler bu repo'da olduğu için her ortam module'ün aynı commit'teki hâlini kullanıyor; dev'de denenmemiş bir module değişikliği, merge edildiği an prod'un da kodudur. Module'ler ayrı bir repo'daysa ortamlar farklı sürümlerde durabilir ve bir sürüm dev'den staging'e, oradan prod'a terfi ettirilebilir. 6. yazıda gördüğümüz Terraform 1.15 özelliği burada işe yarıyor: const variable'ı module source'unda kullanmak.

variable "modules_ref" {
  type        = string
  description = "Tag of the shared modules repository used by this environment."
  const       = true
}

module "api" {
  source = "git::https://github.com/example/bookstore-terraform-modules.git//bookstore-service?ref=${var.modules_ref}"
  # ...
}

Her ortam klasöründe yalnız bir satırlık bir terraform.tfvars var ve terfi, bu satırı değiştiren bir pull request oluyor. Denemede aynı adresi repo'nun lokal bir kopyasına (file://) yönlendirdim:

$ cat terraform.tfvars
modules_ref = "v1.0.0"
$ terraform init
Downloading git::file:///home/user/repos/bookstore-terraform-modules.git?ref=v1.0.0 for api...
- api in .terraform/modules/api/bookstore-service
Terraform has been successfully initialized!

$ echo 'modules_ref = "v1.1.0"' > terraform.tfvars
$ terraform plan

Error: Module source has changed

  on main.tf line 8, in module "api":
   8:   source = "git::file:///home/user/repos/bookstore-terraform-modules.git//bookstore-service?ref=${var.modules_ref}"

The source address was changed since this module was installed. Run
"terraform init" to install all modules required by this configuration.

$ terraform init
Downloading git::file:///home/user/repos/bookstore-terraform-modules.git?ref=v1.1.0 for api...

init, otomatik yüklenen terraform.tfvars'ı okuyup v1.0.0'ı indirdi. Ref'i değiştirince plan, module yeniden kurulmadan çalışmayı reddediyor; böylece eski module ile yeni değerin karıştığı bir plan oluşmuyor. Bu, CI'da her job'da init çalıştırmanın bir sebebi daha.

Üretimde Dikkat

Ortam ayrımı state'te bitmez, kimlikte de olmalı. Production'ı ayrı bir subscription'a koymak ve prod'a apply edebilen identity'yi yalnız prod pipeline'ına vermek, "yanlış klasörde apply" hatasının etkisini sınırlar. State'leri de ayırmak gerekir: dev ve staging aynı storage account'ta farklı key'lerde durabilir, ama prod'un state'i için ayrı bir storage account ve erişim listesi daha güvenli, çünkü 4. yazıda gördüğümüz gibi state secret'ları düz metin tutar.

Ortam klasörleri arasındaki farkı küçük tut. dev ile prod arasındaki her fark (SKU, replikasyon, IP kısıtı) module çağrısında açıkça görünmeli; ortama özgü kaynak gerekiyorsa module'e environment == "prod" koşulu gömmek yerine ortam klasörüne ayrı bir kaynak olarak ekle. Ortamlar ne kadar benzerse staging'de görülen sorun o kadar prod'u temsil eder.

Klasör sayısı arttıkça init, provider indirme ve plan süresi artar. TF_PLUGIN_CACHE_DIR ile provider'ları bir kez indirmek, CI'da da yalnız değişen klasörlerin plan'ını almak bu maliyeti düşürür; 11. yazıda pipeline'ı kurarken bunu yapacağız.

Sık Düşülen Tuzaklar

Tek state'te bütün ortamlar. dev, staging ve prod aynı root module'de, kaynak adlarının başında ortam adıyla duruyorsa her plan üç ortamı birden okur ve her apply üçüne birden dokunabilir. Bir ortamın kilidi diğerlerini de bekletir. Ortamları ayırmanın en kısa yolu bu yazıdaki gibi ayrı state'ler.

Workspace adını kodda koşul olarak kullanmak. Yukarıdaki prd örneğinde gördüğün gibi, beklenmeyen bir ad hata vermez, sessizce varsayılan değere düşer. Workspace kullanıyorsan ortamı yine variable'la ver ve bir validation ile workspace adıyla tutarlı olduğunu kontrol et.

Ortam klasörlerini kopyala-yapıştırla çoğaltıp module'süz bırakmak. Üç klasörde aynı kaynak tanımları elle tutulursa, dev'de yapılan bir düzeltme prod'a hiç ulaşmayabilir. Ortam klasöründe kaynak değil, module çağrısı ve değerler olmalı.

Küçük Bir Deneme

★☆☆ envs/staging'i apply et ve docker exec tfseries-state psql ile bookstore_staging schema'sındaki states tablosuna bak. dev'in schema'sıyla arasında ne fark var?

★★☆ Workspace örneğindeki koda, workspace adının yalnız default, staging ya da prod olabileceğini söyleyen bir kontrol ekle (ör. bir terraform_data kaynağında precondition). prd workspace'inde plan ne diyor?

★★★ Bookstore'un data katmanını ayır: yerel örnekte volume'leri ayrı bir klasöre ve state'e taşıyacak şekilde yeniden düzenle, API klasörü volume adlarını data source ya da naming convention ile bulsun. Hangi adımda bir kaynağı bir state'ten çıkarıp diğerine almak gerekti?

Bir sonraki yazıda tam bu son soruyu cevaplayan araçlara geçiyoruz: elle kurulmuş bir staging'i import block'larıyla koda almak, terraform query ile kaynak bulmak, moved block'larının ne zaman silineceği ve removed ile bir kaynağı silmeden Terraform'un yönetiminden çıkarmak.