> ## Documentation Index
> Fetch the complete documentation index at: https://biznetgio.creations.ren/llms.txt
> Use this file to discover all available pages before exploring further.

# Environment: dev, staging, dan production

> Isolasiin environment pake Terraform workspace dan Pulumi stack

Satu set file konfigurasi, dijalanin ke beberapa environment yang beda-beda, masing-masing punya state dan setting sendiri. Itu ide intinya: `main.tf` atau `index.ts` yang sama dari tutorial [project structure](/id/tutorials/project-structure), diapply tiga kali dengan tiga jawaban berbeda buat "seberapa besar" dan "siapa yang bayar."

## Terraform workspace

Workspace itu slot state yang keisolasi di dalam backend yang sama. Semua workspace share file konfigurasi yang sama.

```bash theme={null}
terraform workspace new staging
terraform workspace new production
terraform workspace list
terraform workspace select staging
```

Reference workspace yang aktif di dalam konfigurasi lu pake `terraform.workspace`. Block `data` dan resource `biznetgio_neolite_keypair` itu persis yang udah dideklarasiin lengkap di [quickstart](/id/quickstart) - yang beda di sini, `product_id` udah bukan `products[0]` lagi. Dipilih dengan sengaja per workspace, pake nama paket beneran dari [katalog NEO Lite](/id/products/neolite):

```hcl theme={null}
data "biznetgio_neolite_products" "all" {}

locals {
  # Nama paket NEO Lite beneran - liat /id/products/neolite buat tabel harga lengkap.
  # dev dikasih tier paling murah dengan sengaja; staging matching sama production
  # biar beneran nangkep bug yang bentuknya kayak production.
  tier_by_workspace = {
    dev        = "XS 1.1" # 1 vCPU / 1 GB - cukup buat smoke-test siklus plan/apply
    staging    = "MS 4.2" # 2 vCPU / 4 GB - tier yang sama kayak production
    production = "MS 4.2"
  }

  matching_products = [
    for p in data.biznetgio_neolite_products.all.products :
    p if p.name == local.tier_by_workspace[terraform.workspace]
  ]

  # Error pas plan kalau nama di atas gak match apapun di akun lu, bukannya
  # diam-diam fallback ke apapun yang jadi products[0].
  selected_product = local.matching_products[0]
}

data "biznetgio_neolite_os_list" "ubuntu" {
  product_id = local.selected_product.product_id
}

resource "biznetgio_neolite_vm" "main" {
  vm_name              = "web-${terraform.workspace}"
  product_id           = local.selected_product.product_id
  select_os            = data.biznetgio_neolite_os_list.ubuntu.oss[0].name
  keypair_id           = biznetgio_neolite_keypair.main.keypair_id
  cycle                = "m"
  ssh_and_console_user = "adminuser"
  console_password     = var.console_password

  # murah dan bisa dibalik pas testing, duit beneran pas production
  pay_with_credit_card = terraform.workspace == "production"
}
```

<Note>
  Confirm `"XS 1.1"` dan `"MS 4.2"` dulu ke output katalog akun lu sendiri - liat [Langkah 1 di overview katalog](/id/products/overview#langkah-1-liat-katalog-lu-sendiri-sebelum-filter-apapun). `select_os` di sini masih pake `oss[0]` karena list OS biasanya pendek dan didominasi satu pilihan yang jelas; pake pola filter nama yang sama kalau di akun lu gak gitu.
</Note>

<Warning>
  Selalu cek workspace mana yang lagi aktif sebelum apply: `terraform workspace show`. Apply ke workspace yang salah itu cara paling umum orang gak sengaja nyenggol production. Coba bungkus `apply` di script atau CI job yang mewajibkan nama workspace sebagai argumen eksplisit, bukan ngandelin workspace mana yang lagi keselect.
</Warning>

Kalau dev, staging, dan production butuh resource yang beneran beda (bukan cuma value yang beda), workspace udah bukan tools yang cocok lagi, karena semua workspace share satu konfigurasi yang sama. Di titik itu, pindah ke satu direktori per environment (`envs/dev`, `envs/staging`, `envs/production`), masing-masing punya konfigurasi backend dan `tfvars` sendiri, tapi tetep manggil [module](/id/tutorials/modules-and-components) yang sama-sama dishare.

## Pulumi stack

Stack itu versi native-nya Pulumi, dan ini primitive yang udah dipake quickstart tanpa disebut namanya.

```bash theme={null}
pulumi stack init dev
pulumi stack init staging
pulumi stack init production
pulumi stack ls
pulumi stack select staging
```

Setiap stack punya file config `Pulumi.<stack>.yaml` sendiri dan state yang independen. Set config per stack secara eksplisit pake `--stack`:

```bash theme={null}
pulumi config set --stack dev biznetgio:apiToken <dev-token> --secret
pulumi config set --stack production biznetgio:apiToken <production-token> --secret
```

Baca nama stack yang aktif di dalam program lu pake `pulumi.getStack()`. `keypair` dan `config` itu persis yang udah dideklarasiin lengkap di [quickstart Pulumi](/id/pulumi-quickstart) - yang beda di sini, `productId` udah bukan `products[0]` lagi. Dipilih dengan sengaja per stack, pake nama paket beneran dari [katalog NEO Lite](/id/products/neolite):

```typescript theme={null}
import * as pulumi from "@pulumi/pulumi";
import * as biznetgio from "@shirasakaren/biznetgio";

const stack = pulumi.getStack();
const products = biznetgio.neoliteProductsOutput();

// Nama paket NEO Lite beneran - liat /id/products/neolite buat tabel harga
// lengkap. dev dikasih tier paling murah dengan sengaja; staging matching
// sama production biar beneran nangkep bug yang bentuknya kayak production.
const tierByStack: Record<string, string> = {
  dev: "XS 1.1", // 1 vCPU / 1 GB - cukup buat smoke-test siklus preview/up
  staging: "MS 4.2", // 2 vCPU / 4 GB - tier yang sama kayak production
  production: "MS 4.2",
};

const productId = products.products.apply((items) => {
  const match = items.find((p) => p.name === tierByStack[stack]);
  if (!match) throw new Error(`no NEO Lite product named '${tierByStack[stack]}' found`);
  return match.productId;
});

const osList = biznetgio.neoliteOsListOutput({ productId: productId });

const vm = new biznetgio.NeoliteVm("main", {
  vmName: `web-${stack}`,
  productId: productId,
  selectOs: osList.oss[0].name,
  keypairId: keypair.keypairId,
  cycle: "m",
  sshAndConsoleUser: "adminuser",
  consolePassword: config.requireSecret("consolePassword"),
  payWithCreditCard: stack === "production",
});
```

<Note>
  Confirm `"XS 1.1"` dan `"MS 4.2"` dulu ke output katalog akun lu sendiri - liat [Langkah 1 di overview katalog](/id/products/overview#langkah-1-liat-katalog-lu-sendiri-sebelum-filter-apapun). `selectOs` di sini masih pake `oss[0]` karena list OS biasanya pendek dan didominasi satu pilihan yang jelas; pake pola filter nama yang sama kalau di akun lu gak gitu.
</Note>

<Warning>
  Aturan yang sama kayak Terraform: cek `pulumi stack ls` (yang aktif ditandain) sebelum `pulumi up`. Pass `--stack <name>` secara eksplisit di CI, jangan ngandelin stack mana yang terakhir keselect di runner itu.
</Warning>

## Pola dev/staging/production yang praktis

Pola yang cocok buat provider ini secara spesifik:

| Environment | `pay_with_credit_card` / `payWithCreditCard` | Tier product                    | Tujuan                                                                                |
| ----------- | -------------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------- |
| dev         | `false`                                      | paling murah yang ada           | validasi plan/preview tanpa charge beneran; resource nyangkut `Pending` sampe dibayar |
| staging     | `true`                                       | tier yang sama kayak production | nangkep behavior dunia nyata sebelum kejadian beneran                                 |
| production  | `true`                                       | tier beneran                    | yang beneran dipake customer                                                          |

Baca [panduan billing](/id/guides/billing) buat tau tepatnya apa yang `pay_with_credit_card = false` lakuin; itu cara paling murah buat gladi resik satu siklus apply/up penuh sebelum komit duit beneran.

## Langkah berikutnya

* [State, remote backend, dan kolaborasi tim](/id/tutorials/state-and-collaboration) - pindahin state tiap environment dari laptop lu
