High Availability Terraform Template
Overview
You can deploy SFTP Gateway in a High Availability (HA) configuration using Terraform.
This article covers deploying a Highly Available setup of SFTP Gateway Professional version 3.7.3
on Azure. The template creates multiple VM instances behind a load balancer with a managed PostgreSQL database for configuration persistence and automatic scaling capabilities.
Note: Make sure you are subscribed to the SFTP Gateway Professional listing in the Azure Marketplace before deploying the Terraform template:
Running the template
This article contains seven files:
main.tf
variables.tf
outputs.tf
01-infrastructure.tf
02-database.tf
03-compute.tf
terraform.tfvars
Create these files on your workstation using the file contents at the bottom of this page. Make adjustments to the terraform.tfvars
file to fit your environment.
After creating all the files and running the deployment, your project directory should look like this (All the auto-created terraform directory and files will not appear until you run terraform init
and terraform apply
):
sftpgw-ha-terraform/
├── main.tf # Provider configuration and resource group
├── variables.tf # All variable definitions
├── outputs.tf # Deployment outputs and access information
├── 01-infrastructure.tf # Networking, security, load balancer, storage
├── 02-database.tf # PostgreSQL database and DNS configuration
├── 03-compute.tf # VM Scale Set and cloud-init configuration
├── terraform.tfvars # Your variable configuration
├── .terraform/ # Terraform provider cache (auto-created)
│ └── providers/
├── terraform.tfstate # Terraform state file (auto-created)
├── .terraform.lock.hcl # Terraform lock file (auto-created)
└── terraform.tfstate.backup # State backup (auto-created)
Then, run the following commands:
terraform init
terraform plan
When you are ready to deploy the template, run:
terraform apply
How does it work
This template provisions the following resources:
Resource Group
: Container for all resourcesVirtual Network
: Isolated network with public and private subnetsNetwork Security Group
: Controls inbound and outbound traffic with security rulesLoad Balancer
: Distributes traffic across VM instances with health probesPublic IP
: Static IP address for external accessKey Vault
: Securely stores database passwords and configurationManaged Identity
: Provides secure access to Azure servicesPostgreSQL Flexible Server
: Managed database for configuration persistencePrivate DNS Zone
: Enables private network access to the databaseVirtual Machine Scale Set
: Multiple VM instances distributed across availability zonesStorage Account
: Contains boot diagnostics and can be used for SFTP data storage
Configuration variables
You can configure the following variables in terraform.tfvars
:
resource_group_name
: The name of the resource group that will be created to house your resourceslocation
: Specify your Azure region. Defaults toEast US
admin_ip_range
: Get your workstation's public IP. Append/32
to specify a single IP range (e.g.,203.0.113.100/32
)linux_admin_username
: Set this to use a specific linux username. Defaults toazureuser
.ssh_public_key
: Set this to your SSH public key for VM accessvm_size
: Size of the VMs. Defaults toStandard_D2s_v3
(2 vCPU, 8 GB RAM)instance_count
: Initial number of VM instances. Defaults to2
db_size
: Size of the PostgreSQL instance. Defaults toB_Standard_B2s
(2 cores, 4 GB RAM)web_admin_username
: Username for the web admin interface (optional)web_admin_password
: Password for the web admin interface (optional, minimum 12 characters)
Accessing your SFTP Gateway
After successful deployment, you can access your SFTP Gateway through multiple methods (HTTPS for Web Interface access/SFTP for connecing as an SFTP user/SSH for connecting as a linux admin):
https://YOUR_PUBLIC_IP
sftp://username@YOUR_PUBLIC_IP
ssh ubuntu@YOUR_PUBLIC_IP -p 2222 # First VM
ssh ubuntu@YOUR_PUBLIC_IP -p 2223 # Second VM (if available)
# Additional VMs use sequential ports: 2224, 2225, etc.
Use the credentials specified in web_admin_username
and web_admin_password
if configured during deployment for logging into the Web Interface.
You can find your public IP address in the Terraform output:
terraform output deployment_summary
Terraform file contents
main.tf
# SFTP Gateway High Availability - Terraform Template
# Production-ready template for customer deployment
terraform {
required_version = ">= 1.9"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 3.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
}
}
provider "azurerm" {
features {
key_vault {
purge_soft_delete_on_destroy = true
recover_soft_deleted_key_vaults = true
}
resource_group {
prevent_deletion_if_contains_resources = false
}
}
}
# Generate unique suffix for resource names
resource "random_uuid" "deployment_seed" {}
locals {
resource_suffix = substr(md5("${var.resource_group_name}-${random_uuid.deployment_seed.result}"), 0, 8)
common_tags = {
Environment = var.environment
Project = "sftpgw"
DeployedBy = "terraform"
Template = "customer-template"
}
}
# Create Resource Group
resource "azurerm_resource_group" "main" {
name = var.resource_group_name
location = var.location
tags = local.common_tags
}
variables.tf
# SFTP Gateway Template - Variables
# All configuration variables for the deployment
# =======================
# REQUIRED VARIABLES
# =======================
variable "resource_group_name" {
description = "Name of the Azure Resource Group"
type = string
}
variable "admin_ip_range" {
description = "IP address CIDR for allowed admin access (e.g., your.ip.address/32)"
type = string
validation {
condition = can(cidrhost(var.admin_ip_range, 0))
error_message = "Admin IP range must be a valid CIDR notation (e.g., 192.168.1.1/32)."
}
}
variable "ssh_public_key" {
description = "SSH public key for VM access"
type = string
sensitive = true
}
# =======================
# OPTIONAL VARIABLES
# =======================
variable "location" {
description = "Azure region for resources"
type = string
default = "East US"
}
variable "environment" {
description = "Environment name (dev, staging, prod)"
type = string
default = "production"
}
# Compute Configuration
variable "linux_admin_username" {
description = "Linux admin username"
type = string
default = "ubuntu"
}
variable "vm_size" {
description = "Size of the virtual machines"
type = string
default = "Standard_D2s_v3"
}
variable "instance_count" {
description = "Number of VM instances in the scale set"
type = number
default = 2
}
variable "availability_zones" {
description = "Availability zones for VM deployment"
type = list(string)
default = ["1", "2", "3"]
}
# Database Configuration
variable "db_size" {
description = "Size of the PostgreSQL instance"
type = string
default = "B_Standard_B2s"
}
variable "db_tier" {
description = "Tier of DB instance (Burstable, GeneralPurpose, MemoryOptimized)"
type = string
default = "Burstable"
validation {
condition = contains(["Burstable", "GeneralPurpose", "MemoryOptimized"], var.db_tier)
error_message = "Database tier must be one of: Burstable, GeneralPurpose, MemoryOptimized."
}
}
variable "db_availability_zone" {
description = "Availability zone for the PostgreSQL database"
type = number
default = 1
validation {
condition = var.db_availability_zone >= 1 && var.db_availability_zone <= 3
error_message = "Database availability zone must be between 1 and 3."
}
}
# Web Admin Configuration (Optional)
variable "web_admin_username" {
description = "Username for web admin interface (optional)"
type = string
default = ""
}
variable "web_admin_password" {
description = "Password for web admin interface (optional, minimum 12 characters)"
type = string
default = ""
sensitive = true
validation {
condition = var.web_admin_password == "" || length(var.web_admin_password) >= 12
error_message = "Web admin password must be at least 12 characters long if provided."
}
}
# Network Configuration
variable "vnet_address_space" {
description = "Address space for the virtual network"
type = string
default = "10.0.0.0/16"
}
variable "default_subnet_prefix" {
description = "Address prefix for the default subnet"
type = string
default = "10.0.0.0/24"
}
variable "private_subnet_prefix" {
description = "Address prefix for the private subnet"
type = string
default = "10.0.1.0/24"
}
# Infrastructure Tiers
variable "ip_tier" {
description = "Tier of public IP (Basic, Standard)"
type = string
default = "Standard"
validation {
condition = contains(["Basic", "Standard"], var.ip_tier)
error_message = "IP tier must be either Basic or Standard."
}
}
variable "load_balancer_tier" {
description = "Tier of load balancer (Basic, Standard)"
type = string
default = "Standard"
validation {
condition = contains(["Basic", "Standard"], var.load_balancer_tier)
error_message = "Load balancer tier must be either Basic or Standard."
}
}
outputs.tf
# outputs.tf
# All deployment outputs for customer reference
# Access Information
output "public_ip_address" {
description = "Public IP address of the SFTP Gateway"
value = azurerm_public_ip.sftpgw.ip_address
}
output "access_urls" {
description = "URLs for accessing the SFTP Gateway"
value = {
web_admin = "https://${azurerm_public_ip.sftpgw.ip_address}"
sftp = "sftp://${azurerm_public_ip.sftpgw.ip_address}:22"
ssh_admin = "ssh://${azurerm_public_ip.sftpgw.ip_address}:2222-2322"
}
}
# Resource Information
output "resource_group_name" {
description = "Name of the created resource group"
value = azurerm_resource_group.main.name
}
output "database_endpoint" {
description = "FQDN of the PostgreSQL server"
value = azurerm_postgresql_flexible_server.sftpgw.fqdn
}
output "scale_set_name" {
description = "Name of the VM scale set"
value = azurerm_linux_virtual_machine_scale_set.sftpgw.name
}
output "current_instance_count" {
description = "Current number of VM instances"
value = azurerm_linux_virtual_machine_scale_set.sftpgw.instances
}
# Deployment Summary
output "deployment_summary" {
description = "Complete deployment information"
value = {
public_ip_address = azurerm_public_ip.sftpgw.ip_address
database_endpoint = azurerm_postgresql_flexible_server.sftpgw.fqdn
instance_count = azurerm_linux_virtual_machine_scale_set.sftpgw.instances
vm_size = azurerm_linux_virtual_machine_scale_set.sftpgw.sku
database_sku = azurerm_postgresql_flexible_server.sftpgw.sku_name
deployment_time = timestamp()
access_info = {
web_admin = "https://${azurerm_public_ip.sftpgw.ip_address}"
sftp = "sftp://username@${azurerm_public_ip.sftpgw.ip_address}"
ssh_admin = "ssh ${var.linux_admin_username}@${azurerm_public_ip.sftpgw.ip_address} -p 2222"
}
next_steps = [
"1. Access web admin interface at the provided URL",
"2. Configure SFTP users and settings",
"3. Test connectivity using the provided access information"
]
}
}
01-infrastructure.tf
# 01-infrastructure.tf
# Core networking, security, and identity resources
data "azurerm_client_config" "current" {}
# Managed Identity for VM access to Key Vault
resource "azurerm_user_assigned_identity" "sftpgw" {
name = "sftpgw-identity-${local.resource_suffix}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
tags = local.common_tags
}
# Role assignment for the managed identity
resource "azurerm_role_assignment" "sftpgw_contributor" {
scope = azurerm_resource_group.main.id
role_definition_name = "Contributor"
principal_id = azurerm_user_assigned_identity.sftpgw.principal_id
}
# Key Vault for storing sensitive data
resource "azurerm_key_vault" "sftpgw" {
name = "sftpgw-kv-${local.resource_suffix}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
tenant_id = data.azurerm_client_config.current.tenant_id
soft_delete_retention_days = 7
purge_protection_enabled = false
sku_name = "standard"
enabled_for_deployment = true
enabled_for_template_deployment = true
enabled_for_disk_encryption = true
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = azurerm_user_assigned_identity.sftpgw.principal_id
secret_permissions = [
"Get",
"List",
]
}
# Allow current user to manage secrets during deployment
access_policy {
tenant_id = data.azurerm_client_config.current.tenant_id
object_id = data.azurerm_client_config.current.object_id
secret_permissions = [
"Get",
"List",
"Set",
"Delete",
"Purge",
"Recover"
]
}
network_acls {
default_action = "Allow"
bypass = "AzureServices"
}
tags = local.common_tags
}
# Virtual Network
resource "azurerm_virtual_network" "sftpgw" {
name = "sftpgw-vnet-${local.resource_suffix}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
address_space = [var.vnet_address_space]
tags = local.common_tags
}
# Default subnet for VMs
resource "azurerm_subnet" "default" {
name = "default"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.sftpgw.name
address_prefixes = [var.default_subnet_prefix]
}
# Private subnet for database
resource "azurerm_subnet" "private" {
name = "private-1"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.sftpgw.name
address_prefixes = [var.private_subnet_prefix]
service_endpoints = ["Microsoft.Storage"]
delegation {
name = "Microsoft.DBforPostgreSQL.flexibleServers"
service_delegation {
name = "Microsoft.DBforPostgreSQL/flexibleServers"
}
}
}
# Network Security Group
resource "azurerm_network_security_group" "sftpgw" {
name = "sftpgw-nsg-${local.resource_suffix}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
security_rule {
name = "admin-ports"
priority = 1010
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_ranges = ["80", "443", "2222"]
source_address_prefix = var.admin_ip_range
destination_address_prefix = "*"
}
security_rule {
name = "default-allow-sftp"
priority = 1000
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
tags = local.common_tags
}
# Public IP for Load Balancer
resource "azurerm_public_ip" "sftpgw" {
name = "sftpgw-ip-${local.resource_suffix}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
allocation_method = "Static"
sku = var.ip_tier
ip_version = "IPv4"
idle_timeout_in_minutes = 4
tags = local.common_tags
}
# Load Balancer
resource "azurerm_lb" "sftpgw" {
name = "sftpgw-lb-${local.resource_suffix}"
location = var.location
resource_group_name = azurerm_resource_group.main.name
sku = var.load_balancer_tier
frontend_ip_configuration {
name = "sftpgw-ip"
public_ip_address_id = azurerm_public_ip.sftpgw.id
}
tags = local.common_tags
}
# Backend Pool
resource "azurerm_lb_backend_address_pool" "sftpgw" {
name = "${azurerm_lb.sftpgw.name}-backend-pool"
loadbalancer_id = azurerm_lb.sftpgw.id
}
# Health Probe
resource "azurerm_lb_probe" "health" {
name = "healthProbe"
loadbalancer_id = azurerm_lb.sftpgw.id
protocol = "Https"
port = 443
request_path = "/backend/actuator/health"
interval_in_seconds = 30
number_of_probes = 2
}
# Load Balancer Rules
resource "azurerm_lb_rule" "http" {
name = "LBRuleHttp"
loadbalancer_id = azurerm_lb.sftpgw.id
protocol = "Tcp"
frontend_port = 80
backend_port = 80
frontend_ip_configuration_name = "sftpgw-ip"
backend_address_pool_ids = [azurerm_lb_backend_address_pool.sftpgw.id]
probe_id = azurerm_lb_probe.health.id
enable_floating_ip = false
disable_outbound_snat = true
idle_timeout_in_minutes = 30
}
resource "azurerm_lb_rule" "https" {
name = "LBRuleHttps"
loadbalancer_id = azurerm_lb.sftpgw.id
protocol = "Tcp"
frontend_port = 443
backend_port = 443
frontend_ip_configuration_name = "sftpgw-ip"
backend_address_pool_ids = [azurerm_lb_backend_address_pool.sftpgw.id]
probe_id = azurerm_lb_probe.health.id
enable_floating_ip = false
disable_outbound_snat = true
idle_timeout_in_minutes = 30
}
resource "azurerm_lb_rule" "sftp" {
name = "LBRuleSftp"
loadbalancer_id = azurerm_lb.sftpgw.id
protocol = "Tcp"
frontend_port = 22
backend_port = 22
frontend_ip_configuration_name = "sftpgw-ip"
backend_address_pool_ids = [azurerm_lb_backend_address_pool.sftpgw.id]
probe_id = azurerm_lb_probe.health.id
enable_floating_ip = false
disable_outbound_snat = true
idle_timeout_in_minutes = 30
}
# Outbound Rule for VM internet access
resource "azurerm_lb_outbound_rule" "network_out" {
name = "networkOut"
loadbalancer_id = azurerm_lb.sftpgw.id
protocol = "All"
enable_tcp_reset = true
allocated_outbound_ports = 2400
idle_timeout_in_minutes = 15
backend_address_pool_id = azurerm_lb_backend_address_pool.sftpgw.id
frontend_ip_configuration {
name = "sftpgw-ip"
}
}
# NAT Pool for SSH admin access
resource "azurerm_lb_nat_pool" "ssh_admin" {
name = "sftpgw-natpool"
loadbalancer_id = azurerm_lb.sftpgw.id
resource_group_name = azurerm_resource_group.main.name
frontend_ip_configuration_name = "sftpgw-ip"
protocol = "Tcp"
frontend_port_start = 2222
frontend_port_end = 2322
backend_port = 2222
}
# Storage Account for Diagnostics
resource "azurerm_storage_account" "diagnostics" {
name = "sftpgwdiag${local.resource_suffix}"
resource_group_name = azurerm_resource_group.main.name
location = var.location
account_tier = "Standard"
account_replication_type = "LRS"
account_kind = "StorageV2"
tags = local.common_tags
}
02-database.tf
# 02-database.tf
# PostgreSQL Flexible Server and related resources
# Local values for database configuration
locals {
# PostgreSQL Flexible Server requires tier prefix: B_ (Burstable), GP_ (GeneralPurpose), MO_ (MemoryOptimized)
tier_prefix = var.db_tier == "Burstable" ? "B" : (var.db_tier == "GeneralPurpose" ? "GP" : "MO")
db_sku_name = "${local.tier_prefix}_${var.db_size}"
}
# Generate secure database password
resource "random_password" "db_password" {
length = 32
special = true
upper = true
lower = true
numeric = true
}
# Store database password in Key Vault
resource "azurerm_key_vault_secret" "db_password" {
name = "dbPassword"
value = random_password.db_password.result
key_vault_id = azurerm_key_vault.sftpgw.id
depends_on = [
azurerm_key_vault.sftpgw,
random_password.db_password
]
tags = local.common_tags
}
# Private DNS Zone for PostgreSQL
resource "azurerm_private_dns_zone" "postgres" {
name = "private.postgres.database.azure.com"
resource_group_name = azurerm_resource_group.main.name
tags = local.common_tags
}
# Link private DNS zone to virtual network
resource "azurerm_private_dns_zone_virtual_network_link" "postgres" {
name = "${azurerm_virtual_network.sftpgw.name}-link"
resource_group_name = azurerm_resource_group.main.name
private_dns_zone_name = azurerm_private_dns_zone.postgres.name
virtual_network_id = azurerm_virtual_network.sftpgw.id
registration_enabled = false
tags = local.common_tags
}
# PostgreSQL Flexible Server
resource "azurerm_postgresql_flexible_server" "sftpgw" {
name = "sftpgw-db-${local.resource_suffix}"
resource_group_name = azurerm_resource_group.main.name
location = var.location
version = "16"
administrator_login = "sftpgw"
administrator_password = random_password.db_password.result
# zone = var.db_availability_zone
# Let Azure choose the fastest available zone
storage_mb = 32768
storage_tier = "P4"
sku_name = local.db_sku_name
backup_retention_days = 7
geo_redundant_backup_enabled = false
public_network_access_enabled = false
maintenance_window {
day_of_week = 0
start_hour = 8
start_minute = 0
}
delegated_subnet_id = azurerm_subnet.private.id
private_dns_zone_id = azurerm_private_dns_zone.postgres.id
depends_on = [azurerm_private_dns_zone_virtual_network_link.postgres]
tags = local.common_tags
lifecycle {
ignore_changes = [
zone,
]
}
}
# Configure PostgreSQL extensions
resource "azurerm_postgresql_flexible_server_configuration" "extensions" {
name = "azure.extensions"
server_id = azurerm_postgresql_flexible_server.sftpgw.id
value = "LTREE"
}
03-compute.tf
# 03-compute.tf
# Virtual Machine Scale Set
# Virtual Machine Scale Set
resource "azurerm_linux_virtual_machine_scale_set" "sftpgw" {
name = "sftpgw-scale-set-${local.resource_suffix}"
resource_group_name = azurerm_resource_group.main.name
location = var.location
sku = var.vm_size
instances = var.instance_count
# Distribution across availability zones
zones = var.availability_zones
overprovision = true
upgrade_mode = "Manual"
disable_password_authentication = true
source_image_reference {
publisher = "thorntechnologiesllc"
offer = "sftpgateway-professional"
sku = "sftpgateway-professional-3-6"
version = "latest"
}
plan {
name = "sftpgateway-professional-3-6"
product = "sftpgateway-professional"
publisher = "thorntechnologiesllc"
}
os_disk {
storage_account_type = "Premium_LRS"
caching = "ReadWrite"
}
admin_username = var.linux_admin_username
admin_ssh_key {
username = var.linux_admin_username
public_key = var.ssh_public_key
}
network_interface {
name = "sftpgw-nic"
primary = true
ip_configuration {
name = "ip-config"
primary = true
subnet_id = azurerm_subnet.default.id
load_balancer_backend_address_pool_ids = [azurerm_lb_backend_address_pool.sftpgw.id]
load_balancer_inbound_nat_rules_ids = [azurerm_lb_nat_pool.ssh_admin.id]
}
network_security_group_id = azurerm_network_security_group.sftpgw.id
}
# User data for cloud-init configuration
custom_data = base64encode(<<-EOT
#cloud-config
repo_update: true
repo_upgrade: all
write_files:
- content: |
#!/bin/bash
export CLOUD_PROVIDER=azure
export ARCHITECTURE=HA
export LOG_GROUP_NAME=logGroup
export SECRET_ID=${azurerm_key_vault_secret.db_password.versionless_id}
export DB_HOST=${azurerm_postgresql_flexible_server.sftpgw.fqdn}
export LOAD_BALANCER_ADDRESSES=${azurerm_public_ip.sftpgw.ip_address}
path: /opt/sftpgw/launch_config.env
permissions: '0644'
runcmd:
# Configure web admin if credentials provided
- |
if [ -n "${var.web_admin_username}" ] && [ -n "${var.web_admin_password}" ]; then
echo "Configuring web admin user..."
# Wait for application to be fully ready (up to 10 minutes)
for i in {1..20}; do
if curl -k -s --connect-timeout 5 https://localhost/backend/actuator/health >/dev/null 2>&1; then
echo "✅ Application is ready, configuring admin user..."
break
fi
echo "⏳ Waiting for application to be ready... ($i/20)"
sleep 30
done
# Additional check: verify we can actually reach the admin endpoint
for i in {1..5}; do
if curl -k -s --connect-timeout 5 https://localhost:8080/3.0.0/admin/config >/dev/null 2>&1; then
echo "✅ Admin endpoint is accessible"
break
fi
echo "⏳ Waiting for admin endpoint... ($i/5)"
sleep 10
done
# Now try to configure
curl -X "POST" "http://localhost:8080/3.0.0/admin/config" \
-H "accept: */*" \
-H "Content-Type: application/json" \
-d "{\"password\": \"${var.web_admin_password}\", \"username\": \"${var.web_admin_username}\"}" \
2>/dev/null || echo "⚠️ Web admin config failed, but will retry automatically"
fi
final_message: "SFTP Gateway cloud-init completed at $TIMESTAMP"
EOT
)
# Managed identity for Key Vault access
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.sftpgw.id]
}
# Boot diagnostics
boot_diagnostics {
storage_account_uri = azurerm_storage_account.diagnostics.primary_blob_endpoint
}
tags = local.common_tags
depends_on = [
azurerm_lb.sftpgw,
azurerm_postgresql_flexible_server.sftpgw,
azurerm_key_vault_secret.db_password
]
lifecycle {
ignore_changes = [
instances,
custom_data,
]
}
}
terraform.tfvars
# ===========================
# REQUIRED CONFIGURATION
# ===========================
# Azure Resource Group (will be created automatically)
resource_group_name = "my-sftpgw-ha-deployment"
# Your IP address for admin access (find with: curl ifconfig.me)
admin_ip_range = "203.0.113.100/32" # Replace with your actual IP/32
# SSH public key for VM access
# Generate with: ssh-keygen -t rsa -b 4096 -f ~/.ssh/sftpgw_key
ssh_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDExample... your-key-here"
linux_admin_username = "azureuser"
# ===========================
# OPTIONAL CONFIGURATION
# ===========================
# Environment name
environment = "production"
# Azure region for deployment
location = "East US"
# Virtual machine settings
vm_size = "Standard_D2s_v3" # 2 vCPU, 8 GB RAM
instance_count = 2 # Number of VMs to start with
# Availability zones (distribute VMs across zones for HA)
availability_zones = ["1", "2", "3"]
# Database sizing (SKU will be auto-prefixed based on tier)
db_size = "Standard_B1ms" # 2 vCPU, 8 GB RAM
db_tier = "Burstable" # Production tier
# Web admin credentials (can be configured later via web interface)
web_admin_username = "WebAdmin"
web_admin_password = "MySecurePassword123" # Minimum 12 characters, avoid $ and & symbols
# Virtual network addressing (usually defaults are fine)
vnet_address_space = "10.0.0.0/16"
default_subnet_prefix = "10.0.0.0/24" # VM subnet
private_subnet_prefix = "10.0.1.0/24" # Database subnet
# Use Standard tier for production (supports availability zones)
ip_tier = "Standard"
load_balancer_tier = "Standard"