Quick-Start Scaffold
Drop these two files into templates/<provider>/<my-template>/ in your template repository, edit the placeholders, push, and trigger a sync. You will have a working custom template in minutes.
This page is the fastest path to "something that runs". The pages that follow (Writing the Terraform Files, template.json Reference) explain the why and the full schema.
Directory Layout
your-template-repo/
└── templates/
└── azure/ # or aws, gcp
└── my-template/
├── main.tf # Terraform definition
└── template.json # UI + validation manifest
The templates/<provider>/<name>/ shape is required — Amnify discovers templates by walking this exact structure.
Skeleton main.tf — Azure
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
subscription_id = var.subscription_id
}
# --- Auto-injected by Amnify (declare; do not remove) ---
variable "subscription_id" {
description = "Azure Subscription ID (auto-injected by Amnify)"
type = string
}
variable "location" {
description = "Azure region (auto-injected by Amnify)"
type = string
default = "West Europe"
}
variable "tags" {
description = "Tags applied by Amnify (auto-injected)"
type = map(string)
default = {}
}
# --- Your variables ---
variable "resource_group_name" {
description = "Name of the resource group"
type = string
}
# --- Your resources ---
resource "azurerm_resource_group" "main" {
name = var.resource_group_name
location = var.location
tags = var.tags
}
# --- Outputs (surfaced on the deployment run) ---
output "resource_group_id" {
value = azurerm_resource_group.main.id
}
Skeleton main.tf — AWS
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.region
}
# --- Auto-injected by Amnify (declare; do not remove) ---
variable "region" {
description = "AWS region (auto-injected by Amnify)"
type = string
default = "eu-west-1"
}
variable "tags" {
description = "Tags applied by Amnify (auto-injected)"
type = map(string)
default = {}
}
# --- Your variables, resources, and outputs go here ---
Do not declare a terraform { backend "..." } block. Amnify injects the remote state backend at runtime during terraform init. Adding your own backend will be overwritten or cause init to fail.
Skeleton template.json
{
"id": "<unique-template-id>",
"name": "<Display Name>",
"description": "<What this template provisions>",
"category": "infrastructure",
"cloud_provider": "azure",
"iac_type": "terraform",
"icon": "Cloud",
"variables": [
{
"name": "resource_group_name",
"type": "string",
"description": "Name of the resource group",
"required": true,
"sensitive": false
}
]
}
Replace the placeholders:
| Placeholder | What to put |
|---|---|
<unique-template-id> | A stable, unique slug (e.g. tpl-azure-my-rg). Used as the database key. |
<Display Name> | What users see in the library card. |
<What this template provisions> | One-sentence summary. |
category | One of infrastructure, application, database, combo. |
cloud_provider | Must match the parent directory: azure, aws, or gcp. |
icon | Any Lucide icon name (e.g. Network, Database, Cloud). |
How a Variable Connects Across Both Files
Every variable lives in two places: declared in main.tf and described in template.json. The name must match exactly. Here is the canonical pattern for a sensitive (secret) variable:
# main.tf
variable "admin_password" {
description = "Admin password"
type = string
}
// template.json
{
"name": "admin_password",
"type": "string",
"description": "Admin password",
"required": true,
"sensitive": true
}
What happens at runtime:
- The user types a value into a masked input in the Amnify UI.
- Amnify encrypts the value with AES-256-GCM, scoped per organization, before storing it.
- During deployment, the worker decrypts the value in memory and writes it into
terraform.tfvars.jsonalongside the non-sensitive variables. - The plaintext is never persisted — only the encrypted ciphertext lives in the database.
Setting sensitive = true on the Terraform variable block does not flag it as a secret in Amnify. Only the sensitive field in template.json does that. You can set both, but the JSON is the source of truth for UI masking and encryption.
Next Steps
- Read Writing the Terraform Files for the full set of rules Amnify imposes.
- Read template.json Reference for every field the manifest supports.
- Walk through the Azure Resource Group example to see a complete, validated template.