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

# Writing the Terraform Files

Your `.tf` files describe the infrastructure to be provisioned. Amnify treats them as standard Terraform — there is no DSL, no preprocessor, and no Amnify-specific syntax. There are, however, a small number of conventions and constraints you must follow so that Amnify can run them inside its execution environment.

***

## Required File Structure

A template directory must contain at least one `.tf` file (conventionally `main.tf`). All `.tf` files in the directory are downloaded together at deploy time and treated as a single Terraform module.

A complete `main.tf` typically contains five sections:

1. The `terraform { required_providers { ... } }` block declaring providers and versions.
2. The `provider "..." { ... }` block configuring the provider.
3. `variable` blocks for every input.
4. `resource` blocks for the infrastructure being provisioned.
5. `output` blocks for values to surface back to users.

You can split these across multiple files (e.g. `providers.tf`, `variables.tf`, `outputs.tf`). The shipped Amnify templates use a single `main.tf` for simplicity.

***

## Provider Setup

### Azure

```hcl theme={null}
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}

provider "azurerm" {
  features {}
  subscription_id = var.subscription_id
}
```

The `subscription_id` is **auto-injected** by Amnify from the project's environment configuration. You only need to declare a `variable "subscription_id"` of type `string` — you do **not** need to expose it in `template.json`.

### AWS

```hcl theme={null}
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}
```

The `region` is auto-injected from the environment configuration (you can still declare it in `template.json` if you want users to override it per deployment — see how [`aws/vpc/template.json`](https://github.com/Amnify/terraform-template/blob/main/templates/aws/vpc/template.json) does this with an `options` array).

***

## Variables Auto-Injected by Amnify

Amnify merges these into `terraform.tfvars.json` at deploy time. Declare a matching `variable` block in your `.tf` file so Terraform accepts the value, but you do not need to (and should not) duplicate them in `template.json`:

| Variable          | Provider | Source                                      | Type          |
| ----------------- | -------- | ------------------------------------------- | ------------- |
| `subscription_id` | Azure    | Project's Azure environment config          | `string`      |
| `region`          | AWS      | Project's AWS environment config            | `string`      |
| `location`        | Azure    | Project default; overridable per deployment | `string`      |
| `tags`            | All      | Project tag policy + deployment metadata    | `map(string)` |

If you want users to override one of these per deployment (e.g. a custom AWS region), add it to the `variables` array in `template.json`. The user-supplied value takes precedence over the auto-injected one in the merge.

***

## Rules Amnify Imposes

These rules exist because Amnify executes `terraform init` and `terraform apply` for you. Violating them will cause init to fail or the wrong behaviour at runtime.

### Do not declare a `backend` block

Amnify injects its own remote backend (Azure Storage) during `terraform init`. If you add your own backend block, init will fail or your state will go to the wrong place.

```hcl theme={null}
# ❌ DO NOT DO THIS
terraform {
  backend "s3" { ... }
}

# ❌ DO NOT DO THIS
terraform {
  backend "azurerm" { ... }
}
```

### Variable names must match `template.json`

Every entry in the `variables` array of `template.json` must correspond to a `variable` block in your `.tf` files with the same `name`. Amnify writes the user-supplied values to `terraform.tfvars.json` keyed by `name`, and Terraform will fail if the variable is not declared.

### Use snake\_case for variable names

All shipped templates use snake\_case (`resource_group_name`, `vpc_id`, `admin_password`). The UI does not care, but consistency makes deployments easier to read in logs and the state file.

### Do not rely on `.tf` `sensitive = true` for secrets

Setting `sensitive = true` on a Terraform `variable` block only affects Terraform's own log output. It does **not** mark the variable as a secret in Amnify. To get UI masking and at-rest encryption, set `"sensitive": true` in `template.json`.

```hcl theme={null}
# This alone is NOT enough for Amnify to treat the value as a secret:
variable "admin_password" {
  type      = string
  sensitive = true   # only affects Terraform CLI output
}
```

### `validation` blocks are honoured but not the primary check

Terraform's `variable` block can include `validation { condition = ... }` rules. These run at `terraform plan` time and will fail the deployment if violated. They are useful as a last-resort safety net, but the **primary** validation should live in `template.json` (via `required`, `options`, `min_items`, etc.) so that errors surface in the UI before plan is even attempted.

***

## Outputs

Anything you declare in an `output` block is captured by Amnify after `terraform apply` and stored on the deployment run. Users can see the values in the run detail view, and downstream pipelines can consume them.

```hcl theme={null}
output "resource_group_id" {
  value = azurerm_resource_group.main.id
}

output "resource_group_name" {
  value = azurerm_resource_group.main.name
}
```

Mark sensitive outputs with `sensitive = true` if you do not want them displayed in plaintext in the UI:

```hcl theme={null}
output "connection_string" {
  value     = azurerm_postgresql_flexible_server.main.fqdn
  sensitive = true
}
```

***

## Validating Locally

Before pushing, validate your template offline:

```bash theme={null}
cd templates/<provider>/<my-template>
terraform init -backend=false
terraform validate
```

`-backend=false` skips the backend init (since you do not declare one and Amnify injects it at deploy time). `terraform validate` will catch syntax errors and undeclared variable references, which are the two most common authoring mistakes.

***

## Next Steps

* [template.json Reference](/cspm-deploy/create-custom-templates/template-json-reference) — the full manifest schema.
* [Variable Types & Secrets](/cspm-deploy/create-custom-templates/variable-types-and-secrets) — how each variable type is rendered and how secrets flow.
