# Best Practices # Terraform Best Practices These practices keep a Terraform-managed Monad organization predictable. New to the provider? Start with the [Terraform overview](/terraform/index). ## Pin the Provider's Minor Version The provider is pre-1.0, so a breaking change ships as a **minor** version bump (for example 0.4 → 0.5), not a major one. Pin to a minor series and move to the next one deliberately: ```hcl monad = { source = "monad-inc/monad" version = "~> 0.5.0" # patch releases only } ``` A looser constraint such as `~> 0.5` accepts every later 0.x release, including breaking ones. Commit `.terraform.lock.hcl` so every run uses the same build. Before raising the pin, read the [changelog](https://github.com/monad-inc/terraform-provider-monad/blob/main/CHANGELOG.md), which gives the migration path for each breaking change, and review the resulting `terraform plan` before applying it. ## Keep Credentials Out of Your Files - Supply the API key through `MONAD_API_TOKEN` or a `sensitive` variable, never a literal in the provider block. - Supply secret values through `sensitive` variables (`TF_VAR_*`, a CI secret, or a secrets-manager data source), never a literal in a `.tf` file. - Reference secrets from components as `{ id = monad_secret..id }`. Secret values are write-only: Terraform sends them to Monad but never writes them to state or plan output. To rotate a secret, change the value you supply and apply; the provider notices the change and updates the secret in place. ## Use One Configuration per Organization API keys are scoped to a single organization, and every resource a provider block creates lands in that provider's `organization_id`. Keep each organization in its own Terraform configuration and state. When one configuration must manage several organizations (a root organization and its teams, for example), declare one provider block per organization with an [alias](https://developer.hashicorp.com/terraform/language/providers/configuration#alias-multiple-provider-configurations), each with that organization's own API key. ## Let Terraform Own What It Manages Terraform compares your configuration with what is in Monad on every plan. A change made in the Monad UI to a Terraform-managed resource shows up in the next plan as drift, and the next apply reverts it. Make changes to managed resources in code; if you change something in the UI to test it, copy the change back into the configuration before the next apply. Two settings behave differently on purpose: - **`enabled` on a pipeline** is adopted from Monad when you leave it out of the configuration, so enabling or disabling a pipeline in the UI is not reverted. Set it explicitly when you want Terraform to enforce it. - **Schema drift detection** is off on any edge whose configuration has no `schema_detection_spec` block. Declare the block on every edge where detection should stay on; see [Schema Drift Detection](/guides/schema-detection). ## Create Pipelines Disabled, Then Enable Create a new pipeline with `enabled = false`. Once secret values are set and the pipeline looks right in the Monad UI, change it to `enabled = true` and apply again. This keeps a half-configured pipeline from pulling data or delivering it to a destination. ## Rehearse in a Sandbox Organization Try a new configuration, a provider upgrade, or a large refactor against a test organization before applying it to production. A clean `terraform plan` shows **what** will change, not the order the API will accept the changes in; the next section is the most common example. ## Replace a Pipeline's Component in Two Steps Monad refuses to delete a component that is still part of a pipeline. If one change both removes a component and points the pipeline at its replacement, Terraform may try the delete before the pipeline update, and the apply stops partway with an error such as `cannot delete an input that is a part of one or more pipelines`. Split the swap into two applies: 1. Add the new component and point the pipeline at it, keeping the old component's resource block in place. Apply. 2. Remove the old component's resource block. Apply. If a combined change has already failed partway, update the pipeline on its own, then apply everything: ```shell terraform apply -target=monad_pipeline. terraform apply ``` The same applies to transforms, enrichments and outputs. ## Give Large Applies Time Monad processes pipeline creation one request at a time, so a configuration that creates many pipelines at once can take a few minutes. Each API call has a five-minute budget by default. Raise it for one resource with a `timeouts` block, or for every call with `request_timeout`: ```hcl resource "monad_pipeline" "large" { # ... timeouts { create = "10m" } } ``` If a pipeline create still runs out of time, the provider looks for the pipeline the API finished creating and adopts it into state rather than leaving a duplicate for the next apply to create. Running the first apply of a large configuration with `terraform apply -parallelism=1` avoids the contention altogether. ## Run Terraform from CI Treat the configuration like any other infrastructure code: - Keep state in a [remote backend](https://developer.hashicorp.com/terraform/language/backend) with locking, so two runs cannot apply at once. State holds no secret values, but it does hold your full pipeline configuration, so restrict access to it. - Post `terraform plan` output on each pull request for review. - Apply only from the main branch, using a dedicated API key for CI so it can be rotated or revoked on its own.