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:
- The
terraform { required_providers { ... } }block declaring providers and versions. - The
provider "..." { ... }block configuring the provider. variableblocks for every input.resourceblocks for the infrastructure being provisioned.outputblocks 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
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
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 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.
# ❌ 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.
# 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.
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:
output "connection_string" {
value = azurerm_postgresql_flexible_server.main.fqdn
sensitive = true
}
Validating Locally
Before pushing, validate your template offline:
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 — the full manifest schema.
- Variable Types & Secrets — how each variable type is rendered and how secrets flow.