A composite module that provisions an Azure Batch account and its tightly-coupled child resources β applications, certificates, pools, and jobs β behind one deeply-typed, secure-by-default boundary. Built for AzureRM v4.x.
This module creates and wires together everything an Azure Batch deployment needs:
- π§± A Batch account (
azurerm_batch_account) β the control-plane resource, namedthis. - π¦ Applications (
azurerm_batch_application) β package definitions for the code your tasks run. - π Certificates (
azurerm_batch_certificate) β # DEPRECATED by Azure (see Troubleshooting), exposed for completeness. - π₯οΈ Pools (
azurerm_batch_pool) β the compute-node fleets, with the full provider surface: fixed/auto scale, container configuration, mounts, network configuration, security profile, start tasks, user accounts, extensions, and identities. - ποΈ Jobs (
azurerm_batch_job) β task containers that target a pool created here (bypool_key) or an external pool (bybatch_pool_id). - π Secure defaults β
public_network_access_enabled = false,network_profiledefault actions ofDeny, and a sensitive-split for certificate secret material so private keys never appear in plan output.
π‘ Why it matters: one module call stands up the account, its compute pools, and the jobs that run on them β with no manual ID plumbing between them β while the type system rejects a malformed pool definition long before a single API call.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- β Star this repository to help others discover this Terraform module.
- π€ Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
RG["tf-mod-azure-resource-group<br/>(resource group Β· location)"]
SA["tf-mod-azure-storage-account<br/>(auto-storage Β· packages)"]
KV["tf-mod-azure-key-vault<br/>(CMK key Β· vault ref)"]
UAI["tf-mod-azure-user-assigned-identity<br/>(node / account identity)"]
NET["tf-mod-azure-subnet<br/>(pool VNet integration)"]
BATCH["tf-mod-azure-batch<br/>(this module)"]
RBAC["tf-mod-azure-role-assignment"]
PE["tf-mod-azure-private-endpoint"]
RG -->|"resource_group_name Β· location"| BATCH
SA -->|"storage_account_id"| BATCH
KV -->|"key_vault_reference Β· encryption.key_vault_key_id"| BATCH
UAI -->|"identity_ids Β· *_assigned_identity_id"| BATCH
NET -->|"pools[*].network_configuration.subnet_id"| BATCH
BATCH -->|"identity_principal_id"| RBAC
BATCH -->|"id"| PE
style BATCH fill:#8957E5,color:#fff
style RG fill:#0078D4,color:#fff
This module consumes a resource group, optional storage account, Key Vault references, managed identities, and pool subnets from its siblings, and emits the account id / name / account_endpoint, the identity principal/tenant IDs, and per-collection ID maps (application_ids, pool_ids, job_ids, β¦) for downstream role assignments, private endpoints, and monitoring β see the Cross-Module Contract.
flowchart TD
ACCT["azurerm_batch_account.this<br/>(primary β 1)"]
APP["azurerm_batch_application.application<br/>(for_each β 0..N)"]
CERT["azurerm_batch_certificate.certificate<br/>(for_each β 0..N Β· DEPRECATED)"]
POOL["azurerm_batch_pool.pool<br/>(for_each β 0..N)"]
JOB["azurerm_batch_job.job<br/>(for_each β 0..N)"]
ACCT -->|"account_name"| APP
ACCT -->|"account_name"| CERT
ACCT -->|"account_name"| POOL
POOL -->|"pool_key β batch_pool_id"| JOB
ACCT -.->|"or external batch_pool_id"| JOB
style ACCT fill:#8957E5,color:#fff
style CERT stroke:#C9510C,stroke-width:2px,stroke-dasharray:4 3
Resource inventory β what Terraform actually creates when you call this module:
- π§±
azurerm_batch_account.thisβ primary control-plane resource; everything else is addressed through its name. - π¦
azurerm_batch_application.applicationβfor_eachovervar.applications; one application package definition per key. - π
azurerm_batch_certificate.certificateβfor_eachovervar.certificates; secret material joined in from the sensitivevar.certificate_secretsmap. # DEPRECATED β emits a provider deprecation warning by design. - π₯οΈ
azurerm_batch_pool.poolβfor_eachovervar.pools; the compute fleet, rendered through ~20dynamicblocks. - ποΈ
azurerm_batch_job.jobβfor_eachovervar.jobs;batch_pool_idresolved frompool_key(intra-module) or supplied directly.
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
| AzureRM provider | >= 4.0, < 5.0 |
| Azure API | Microsoft.Batch 2024-07-01 |
Schema notes that bite on the 4.x line:
β οΈ azurerm_batch_certificateis deprecated β the Azure Batch Certificates feature retired 2024-02-29 and the resource is removed in provider v5.0.terraform validateemits a deprecation warning whenever any certificate is declared. Plan new work around Key Vault VM extensions / pool certificate references.- βΉοΈ No
skuargument. Unlike most Azure services,azurerm_batch_accountexposes no SKU. The tier behaviour is governed entirely bypool_allocation_mode. - βΉοΈ Cross-variable validations fire at
plan, notterraform validate. TheUserSubscription β key_vault_referenceandcertificates β certificate_secretschecks reference a second variable, so they evaluate at plan time. - βΉοΈ Pool
identityisUserAssigned-only in the provider; the module fixestypeand exposes justidentity_ids. (Account identity acceptsSystemAssignedorUserAssigned.)
Pin Terraform
>= 1.12.0β the module relies onoptionalobject defaults and cross-variablevalidationblocks.
tf-mod-azure-batch/
βββ providers.tf # Terraform + AzureRM version pins (no provider config)
βββ variables.tf # Deeply-typed inputs, heredoc schemas, enum + cross-field validations
βββ main.tf # Primary `this` + 4 for_each collections, total renderer (dynamic blocks)
βββ outputs.tf # Composition contract: account IDs/keys/identity + per-collection ID maps
βββ SCOPE.md # Composite scope contract (allow-list, consumes/emits, gotchas)
βββ README.md # You are here
The smallest call that produces a real, usable account with one pool:
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
pools = {
linux = {
vm_size = "STANDARD_A1_V2"
node_agent_sku_id = "batch.node.ubuntu 20.04"
storage_image_reference = {
publisher = "canonical"
offer = "0001-com-ubuntu-server-focal"
sku = "20_04-lts"
version = "latest"
}
fixed_scale = {
target_dedicated_nodes = 2
}
target_node_communication_mode = "Simplified" # Classic retired 2026-03-31
}
}
}π
public_network_access_enableddefaults tofalseβ the account is private out of the box. Set it explicitly totrueonly if you have a justified reason.
Sourced directly from SCOPE.md.
| Input | Type | Source |
|---|---|---|
π·οΈ resource_group_name |
string |
tf-mod-azure-resource-group.name |
π location |
string |
tf-mod-azure-resource-group.location |
πΎ storage_account_id |
string |
tf-mod-azure-storage-account.id |
πͺͺ storage_account_node_identity |
string |
tf-mod-azure-user-assigned-identity.id |
πͺͺ identity.identity_ids |
list(string) |
tf-mod-azure-user-assigned-identity.id |
π key_vault_reference |
object({ id, url }) |
tf-mod-azure-key-vault (id + vault URI) β UserSubscription mode |
π encryption.key_vault_key_id |
string |
tf-mod-azure-key-vault-key.id β CMK |
π pools[*].network_configuration.subnet_id |
string |
tf-mod-azure-subnet.id |
| Output | Description | Consumed by |
|---|---|---|
id |
Full Batch account resource ID | tf-mod-azure-role-assignment (scope), tf-mod-azure-private-endpoint, diagnostic settings |
name |
Batch account name | Dependent *_account_name references |
resource_group_name |
Resource group name (passthrough) | Sibling modules in the same RG |
location |
Azure region | Region-aware siblings |
account_endpoint |
Batch service endpoint | Application clients / job schedulers |
primary_access_key π |
Primary shared key (conditional) | Key Vault secret modules, app config |
secondary_access_key π |
Secondary shared key (conditional) | Key Vault secret modules, app config |
identity_principal_id |
Account identity principal ID (null if no identity) | tf-mod-azure-role-assignment (principal_id) |
identity_tenant_id |
Account identity tenant ID (null if no identity) | Cross-tenant wiring |
application_ids |
Map: app handle β resource ID | Package upload pipelines |
certificate_ids |
Map: cert handle β resource ID (# DEPRECATED) | Pool certificate references |
pool_ids |
Map: pool handle β resource ID | Jobs (intra-module via pool_key), scheduling |
job_ids |
Map: job handle β resource ID | Job-monitoring automation |
1οΈβ£ Minimal β account only
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
}π‘ The empty call creates just the account β private, with Microsoft-managed storage and no pools. Every child collection defaults to
{}.
2οΈβ£ Linked auto-storage with managed-identity auth
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
storage_account_id = module.batch_storage.id
storage_account_authentication_mode = "BatchAccountManagedIdentity" # preferred over StorageKeys
storage_account_node_identity = module.batch_identity.id
identity = {
type = "UserAssigned"
identity_ids = [module.batch_identity.id]
}
}π
BatchAccountManagedIdentitykeeps storage keys out of the picture. The identity needs a role (e.g. Storage Blob Data Contributor) on the auto-storage account or application-package uploads fail with HTTP 409.
3οΈβ£ System-assigned identity + customer-managed-key encryption
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
identity = {
type = "SystemAssigned"
}
encryption = {
key_vault_key_id = module.batch_cmk.versionless_id # versioned or versionless both supported
}
}π CMK requires the account identity to hold Key Get / Wrap / Unwrap on a Key Vault that has soft-delete + purge protection enabled. The module wires the key URL; the RBAC/Key Vault config is the caller's.
4οΈβ£ UserSubscription pool allocation mode
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
pool_allocation_mode = "UserSubscription"
key_vault_reference = {
id = module.batch_kv.id
url = module.batch_kv.vault_uri
}
}
β οΈ UserSubscriptionrequireskey_vault_reference(enforced at plan time by this module). The subscription must also be registered with Azure Batch, and the "Microsoft Azure Batch" service principal needs Contributor on the subscription. In this mode your subscription vCPU quotas apply β not the Batch-account core quotas.
5οΈβ£ Network-locked account (private + IP allow-list)
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
public_network_access_enabled = false
network_profile = {
account_access = {
default_action = "Deny"
ip_rule = [
{ ip_range = "10.20.0.0/16" },
]
}
node_management_access = {
default_action = "Deny"
ip_rule = [
{ ip_range = "10.20.0.0/16" },
]
}
}
}π
default_actiondefaults toDenyfor both access blocks; theip_rule.actionvalue can only beAllow. Pair with atf-mod-azure-private-endpointon the emittedidfor full lockdown.
6οΈβ£ Auto-scaling pool
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
pools = {
autoscale = {
vm_size = "STANDARD_D2_V3"
node_agent_sku_id = "batch.node.ubuntu 20.04"
storage_image_reference = {
publisher = "canonical"
offer = "0001-com-ubuntu-server-focal"
sku = "20_04-lts"
version = "latest"
}
auto_scale = {
evaluation_interval = "PT15M"
formula = <<-EOF
startingNumberOfVMs = 1;
maxNumberofVMs = 25;
pendingTaskSamplePercent = $PendingTasks.GetSamplePercent(180 * TimeInterval_Second);
pendingTaskSamples = pendingTaskSamplePercent < 70 ? startingNumberOfVMs: avg($PendingTasks.GetSample(180 * TimeInterval_Second));
$TargetDedicatedNodes = min(maxNumberofVMs, pendingTaskSamples);
EOF
}
}
}
}
β οΈ A pool may set at most one offixed_scale/auto_scale(validated). Provide neither and the pool defaults to one dedicated node.
7οΈβ£ Container pool with a start task
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
pools = {
containers = {
vm_size = "STANDARD_D2_V3"
node_agent_sku_id = "batch.node.ubuntu 20.04"
storage_image_reference = {
publisher = "microsoft-azure-batch"
offer = "ubuntu-server-container"
sku = "20-04-lts"
version = "latest"
}
fixed_scale = { target_dedicated_nodes = 2 }
container_configuration = {
type = "DockerCompatible"
container_image_names = ["myregistry.azurecr.io/worker:1.0"]
container_registries = [{
registry_server = "myregistry.azurecr.io"
user_assigned_identity_id = module.acr_pull_identity.id # prefer identity over username/password
}]
}
start_task = {
command_line = "/bin/bash -c 'echo provisioning'"
wait_for_success = true
user_identity = {
auto_user = {
elevation_level = "Admin"
scope = "Pool"
}
}
}
}
}
}π Use
user_assigned_identity_idon registries instead ofuser_name/passwordso credentials never enter Terraform state.
8οΈβ£ VNet-integrated pool with endpoint configuration
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
pools = {
vnet = {
vm_size = "STANDARD_D2_V3"
node_agent_sku_id = "batch.node.ubuntu 20.04"
storage_image_reference = {
publisher = "canonical"
offer = "0001-com-ubuntu-server-focal"
sku = "20_04-lts"
version = "latest"
}
fixed_scale = { target_dedicated_nodes = 1 }
network_configuration = {
subnet_id = module.batch_subnet.id
public_address_provisioning_type = "NoPublicIPAddresses"
endpoint_configuration = [{
name = "ssh"
backend_port = 22
protocol = "TCP"
frontend_port_range = "1000-1100"
network_security_group_rules = [{
access = "Allow"
priority = 150
source_address_prefix = "10.0.0.0/16"
}]
}]
}
}
}
}
β οΈ network_configurationis # IMMUTABLE β changing the subnet forces pool recreation.frontend_port_rangemust span at least 100 ports and avoid the 50000β55000 reserved range.
9οΈβ£ Hardened pool β trusted launch + host encryption
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
pools = {
secure = {
vm_size = "STANDARD_D2S_V3"
node_agent_sku_id = "batch.node.ubuntu 22.04"
storage_image_reference = {
publisher = "canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
fixed_scale = { target_dedicated_nodes = 1 }
security_profile = {
security_type = "trustedLaunch"
secure_boot_enabled = true
vtpm_enabled = true
host_encryption_enabled = true
}
target_node_communication_mode = "Simplified"
}
}
}π
security_profileis # IMMUTABLE (creation-only).security_typemust be set to enablesecure_boot_enabled/vtpm_enabled.
π Applications
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
storage_account_id = module.batch_storage.id
storage_account_authentication_mode = "StorageKeys"
applications = {
ffmpeg = {
display_name = "FFmpeg Transcoder"
allow_updates = false # lock packages once published
default_version = "6.0"
}
solver = {}
}
}βΉοΈ A linked
storage_account_idis required for application packages (Batch stores package binaries in auto-storage). Max 200 applications per account.
1οΈβ£1οΈβ£ Certificates (DEPRECATED) β sensitive-split inputs
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
# Non-sensitive, iterable structural fields
certificates = {
signing = {
format = "Pfx"
thumbprint = "42C107874FD0E4A9583292A2F1098E8FE4B2EDDA"
# thumbprint_algorithm defaults to "SHA1"
}
}
# Sensitive secret material, keyed identically
certificate_secrets = {
signing = {
certificate = filebase64("${path.module}/signing.pfx")
password = var.signing_cert_password
}
}
}
β οΈ Deprecated by Azure β prefer Key Vault for new work. Thecertificatesmap holds structural fields so Terraform canfor_eachover it; the encoded cert + password live in thesensitivecertificate_secretsmap. Everycertificateskey must have a matchingcertificate_secretskey (validated).
1οΈβ£2οΈβ£ Jobs targeting in-module and external pools
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
pools = {
primary = {
vm_size = "STANDARD_A1_V2"
node_agent_sku_id = "batch.node.ubuntu 20.04"
storage_image_reference = {
publisher = "canonical"
offer = "0001-com-ubuntu-server-focal"
sku = "20_04-lts"
version = "latest"
}
fixed_scale = { target_dedicated_nodes = 1 }
}
}
jobs = {
nightly = {
pool_key = "primary" # resolved to azurerm_batch_pool.pool["primary"].id
priority = 100
task_retry_maximum = 3
common_environment_properties = { STAGE = "prod" }
}
external = {
batch_pool_id = module.other_batch.pool_ids["gpu"] # a pool from another module
}
}
}
β οΈ Each job must set exactly one ofpool_key/batch_pool_id(validated).priorityranges β1000β¦1000. Jobs are # IMMUTABLE β any change recreates them.
1οΈβ£3οΈβ£ for_each at scale β many pools from one map
locals {
# Stable keys are identity β never renumber them (renaming a key destroys & recreates the pool).
pool_defs = {
cpu-small = { vm_size = "STANDARD_A1_V2", nodes = 2 }
cpu-large = { vm_size = "STANDARD_D4_V3", nodes = 4 }
burst-pool = { vm_size = "STANDARD_D2_V3", nodes = 0 }
}
}
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatch001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
pools = {
for k, v in local.pool_defs : k => {
vm_size = v.vm_size
node_agent_sku_id = "batch.node.ubuntu 20.04"
storage_image_reference = {
publisher = "canonical"
offer = "0001-com-ubuntu-server-focal"
sku = "20_04-lts"
version = "latest"
}
fixed_scale = { target_dedicated_nodes = v.nodes }
target_node_communication_mode = "Simplified"
}
}
}π‘ The map key becomes the pool name unless you set
name. Keep keys descriptive and permanent β they are the resource's identity in state.
1οΈβ£4οΈβ£ ποΈ End-to-end composition (mandatory)
module "resource_group" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-resource-group?ref=v1.0.0"
name = "rg-batch-prod"
location = "eastus2"
}
module "batch_identity" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-user-assigned-identity?ref=v1.0.0"
name = "id-batch-prod"
resource_group_name = module.resource_group.name
location = module.resource_group.location
}
module "batch_storage" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-storage-account?ref=v1.0.0"
name = "stbatchprod001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
}
module "batch" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-batch?ref=v1.0.0"
name = "caseybatchprod001"
resource_group_name = module.resource_group.name
location = module.resource_group.location
storage_account_id = module.batch_storage.id
storage_account_authentication_mode = "BatchAccountManagedIdentity"
storage_account_node_identity = module.batch_identity.id
identity = {
type = "UserAssigned"
identity_ids = [module.batch_identity.id]
}
public_network_access_enabled = false
pools = {
workers = {
vm_size = "STANDARD_D2_V3"
node_agent_sku_id = "batch.node.ubuntu 22.04"
storage_image_reference = {
publisher = "canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts"
version = "latest"
}
fixed_scale = { target_dedicated_nodes = 3 }
target_node_communication_mode = "Simplified"
identity = { identity_ids = [module.batch_identity.id] }
}
}
jobs = {
etl = {
pool_key = "workers"
priority = 100
}
}
tags = { workload = "batch", env = "prod" }
}
# Grant the account identity access to its CMK / storage downstream
module "batch_storage_role" {
source = "git::https://github.com/microsoftexpert/tf-mod-azure-role-assignment?ref=v1.0.0"
scope = module.batch_storage.id
role = "Storage Blob Data Contributor"
principal_id = module.batch.identity_principal_id
}ποΈ The full chain: resource group β identity + storage β batch (account + pool + job) β role assignment β wired entirely through module outputs, no hand-copied IDs.
Core (account): name, resource_group_name, location, pool_allocation_mode, public_network_access_enabled.
Storage & auth: storage_account_id, storage_account_authentication_mode, storage_account_node_identity, allowed_authentication_modes.
Identity & security: identity, key_vault_reference, encryption, network_profile.
Collections: applications, certificates + certificate_secrets π, pools, jobs.
Lifecycle: tags, timeouts.
Full object schemas for the map inputs
# applications β map keyed by a stable handle (key = name unless overridden)
map(object({
name = optional(string) # defaults to map key
allow_updates = optional(bool, true)
default_version = optional(string)
display_name = optional(string)
}))
# certificates β NON-sensitive structural fields (# DEPRECATED feature)
map(object({
format = string # "Cer" | "Pfx"
thumbprint = string # # IMMUTABLE
thumbprint_algorithm = optional(string, "SHA1") # only "SHA1". # IMMUTABLE
}))
# certificate_secrets β SENSITIVE, keyed identically to certificates
map(object({
certificate = string # base64 contents
password = optional(string) # only valid when format = "Pfx"
}))
# pools β abbreviated; see variables.tf for the exhaustive nested schema
map(object({
name = optional(string) # defaults to key. # IMMUTABLE
vm_size = string # # IMMUTABLE
node_agent_sku_id = string # # IMMUTABLE
display_name = optional(string) # # IMMUTABLE
max_tasks_per_node = optional(number, 1) # # IMMUTABLE
inter_node_communication = optional(string, "Enabled") # "Enabled" | "Disabled"
license_type = optional(string) # "Windows_Server" | "Windows_Client"
os_disk_placement = optional(string) # "CacheDisk"
target_node_communication_mode = optional(string) # "Classic" | "Default" | "Simplified"
stop_pending_resize_operation = optional(bool)
metadata = optional(map(string), {})
storage_image_reference = object({ publisher, offer, sku, version, id (all optional) }) # required. # IMMUTABLE
fixed_scale | auto_scale # at most one
data_disks, disk_encryption, extensions, identity, start_task, container_configuration,
mount, network_configuration, node_placement, security_profile, task_scheduling_policy,
user_accounts, windows
}))
# jobs β map keyed by a stable handle; exactly one of pool_key / batch_pool_id
map(object({
name = optional(string) # defaults to key
pool_key = optional(string) # key into this module's pools
batch_pool_id = optional(string) # explicit external pool ID
display_name = optional(string)
priority = optional(number, 0) # -1000.. 1000
task_retry_maximum = optional(number) # 0 = none, -1 = unlimited
common_environment_properties = optional(map(string))
}))| Output | Description | Notes |
|---|---|---|
id |
Full Batch account resource ID | β |
name |
Batch account name | β |
resource_group_name |
Resource group (passthrough) | β |
location |
Azure region | β |
account_endpoint |
Batch service endpoint | β |
primary_access_key |
Primary shared key | π sensitive; null unless SharedKey + BatchService |
secondary_access_key |
Secondary shared key | π sensitive; same conditions as primary |
identity_principal_id |
Account identity principal/object ID | null when no identity configured |
identity_tenant_id |
Account identity tenant ID | null when no identity configured |
application_ids |
Map: handle β application ID | {} when none |
certificate_ids |
Map: handle β certificate ID | {} when none; # DEPRECATED feature |
pool_ids |
Map: handle β pool ID | {} when none |
job_ids |
Map: handle β job ID | {} when none |
- One primary, four collections.
azurerm_batch_account.thisis the only singleton; applications, certificates, pools, and jobs arefor_eachcollections overmap(object(...)). Nocountanywhere β order changes never cascade into spurious re-creates. for_eachkeys are identity. Each collection is keyed by a caller-supplied string; that key is the resource address in state. Renaming a key destroys and recreates the resource (and, for pools, any jobs that referenced it bypool_key). Treat keys as permanent. The resourcenamedefaults to the key but can be overridden withnamewhen you want a logical handle distinct from the Azure name.- Sensitive-split for certificate secrets. Terraform forbids
for_eachover asensitivevalue, so the iterable structural fields live incertificates(non-sensitive) and the private-key material lives incertificate_secrets(sensitive = true), keyed identically.main.tfjoins them per entry (var.certificate_secrets[each.key].certificate). Because Terraform rejects mutual cross-variable validations as a dependency cycle, the "every cert has secret material" check lives one-directionally oncertificates. This is the same pattern the APIMtf-mod-azure-apim-sharedmodule uses fornamed_values/named_value_secretsβ and it means nononsensitivehack is needed here. - The "SKU" is
pool_allocation_mode. Batch has noskuargument.BatchService(default) allocates pools in Microsoft-managed subscriptions and enforces Batch-account core quotas;UserSubscriptionallocates VMs directly in your subscription (enabling Reserved Instances, Azure Policy on the scale sets, and shared subscription core quota) and requireskey_vault_reference. Switching modes is # IMMUTABLE β it forces a new account, so choose at creation. - Immutable vs in-place. Account
name/location/pool_allocation_modeand most pool fields (vm_size,node_agent_sku_id,storage_image_reference,network_configuration,security_profile,max_tasks_per_node) are ForceNew β changing them recreates the resource. Scale settings, metadata, and start tasks update in place. Every ForceNew field is labelled# IMMUTABLEinvariables.tf. - Intra-module jobβpool wiring. A job's
pool_keyis resolved inmain.tftoazurerm_batch_pool.pool[key].id, so a single call can stand up pools and the jobs that run on them with no manual ID copying. Usebatch_pool_idfor pools owned by another module. - Implicit ordering. Every child references
azurerm_batch_account.this.name(jobs additionally reference pool IDs), so Terraform derives create/destroy order with no explicitdepends_on. - Conditional outputs. Access keys and identity IDs are wrapped in
try(..., null)because they only exist under specific configurations β the outputs are always valid, returningnullwhen the feature is off.
- π Secure by default β private network access,
Denynetwork rules, identity-based storage auth encouraged over shared keys. - π Secrets stay out of plan output β
certificate_secretsand the access-key outputs aresensitive; identity/registry references are preferred over inline passwords. - 𧬠The type is the contract β deeply-typed
objectschemas withoptionaldefaults; a malformed pool is a plan-time type error, not a runtime failure. - π‘οΈ Validation at the boundary β enums, closed value sets, and the provider's hard cross-field rules (
UserSubscription β KeyVault, single scale block, exactly-one job pool reference) are enforced before any API call. - π― Small blast radius β
for_eachcollections mean one bad pool doesn't re-plan the account or the other pools. - πͺ
main.tfis a total renderer β pure projection of typed input onto provider blocks viadynamicblocks andtry(x, null); no business logic.
terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan
terraform apply
terraform output
β οΈ Always pin the module with?ref=v1.0.0β never a branch. A tag is immutable once consumers reference it; a branch can change underneath every caller.
The offline proof gate (no cloud credentials required):
terraform fmt -check # zero formatting differences
terraform validate # valid configuration (emits the expected azurerm_batch_certificate deprecation warning)
tflint # lint for provider-specific issuesβΉοΈ
terraform validatepasses with a single deprecation warning forazurerm_batch_certificatewhenever certificates are declared β this is expected and documented, not an error.
account_endpoint = "caseybatchprod001.eastus2.batch.azure.com"
id = "/subscriptions/β¦/resourceGroups/rg-batch-prod/providers/Microsoft.Batch/batchAccounts/caseybatchprod001"
identity_principal_id = "00000000-0000-0000-0000-000000000000"
job_ids = {
"etl" = "/subscriptions/β¦/batchAccounts/caseybatchprod001/jobs/etl"
}
name = "caseybatchprod001"
pool_ids = {
"workers" = "/subscriptions/β¦/batchAccounts/caseybatchprod001/pools/workers"
}
primary_access_key = (sensitive value)
- π₯ Pool applies cleanly but never reaches its node count (
AccountCoreQuotaReached/AccountLowPriorityCoreQuotaReached/AccountVMSeriesCoreQuotaReached). New Batch accounts in many regions and subscription types ship with core quotas reduced to 0. Terraform creates the account and pool resource successfully, but the allocation state staysresizingforever. Fix: request a per-VM-series core quota increase for the Batch account (Azure portal β Batch account β Quotas, or the Azure Quota REST API) β it's free and usually granted in minutes to two business days. This is the single most common "it deployed but nothing runs" surprise. - π₯
Classicnode communication mode rejected / deprecated. The classic node communication model retired 2026-03-31. Settarget_node_communication_mode = "Simplified"on new pools (it also reduces the required inbound/outbound connectivity and removes the Azure Storage outbound dependency). - π§
azurerm_batch_certificatedeprecation warning on everyvalidate/plan. Expected β the Azure Batch Certificates feature retired 2024-02-29 and the resource is removed in provider v5.0. If you're on the>= 4.0, < 5.0pin it still works; plan migration to Key Vault VM extensions before adopting v5.0. - π§ HTTP 409 when uploading an application package. With
storage_account_authentication_mode = "BatchAccountManagedIdentity", the account's managed identity needs a data-plane role (e.g. Storage Blob Data Contributor) on the linked auto-storage account. Grant it viatf-mod-azure-role-assignmentonidentity_principal_id. - π§
UserSubscriptionaccount fails to create. The subscription must be registered with the Azure Batch resource provider, the "Microsoft Azure Batch" service principal needs Contributor on the subscription, andkey_vault_referenceis mandatory (the module enforces the last one at plan time). In this mode the Batch core quotas don't apply β your subscription regional/per-series vCPU quotas do. - π§ CMK (
encryption) fails to activate. The referenced Key Vault must have soft-delete and purge protection enabled, and the account identity (system- or user-assigned) needs Key Get / Wrap / Unwrap. Both versioned and versionless key URLs are accepted. - π¨
primary_access_key/secondary_access_keycome backnull. Shared keys are only populated whenallowed_authentication_modesincludes"SharedKey"andpool_allocation_mode = "BatchService". AAD-only accounts (the more secure posture) returnnullby design. - π¨ Renaming a map key recreates a resource. Changing a key in
pools/jobs/applications/certificatesis a destroy-and-recreate β and renaming a pool key orphans any job referencing it bypool_key. Add new keys; don't renumber existing ones. - π¨ Pool/job limits. Max 200 applications and 100β300 active jobs per account (completed jobs are unlimited), 100 pools (default; 500 max), 10 mounts per node, 12 certificates per pool, and a 180-day maximum task lifetime. Hitting these is a quota/limit issue, not a Terraform error.
- Terraform Registry β
azurerm_batch_account,azurerm_batch_pool,azurerm_batch_application,azurerm_batch_certificate,azurerm_batch_job - Microsoft Learn β What is Azure Batch? and Batch service workflow and resources
- Microsoft Learn β Batch service quotas and limits
- Microsoft Learn β Azure Batch best practices (pool allocation & node communication modes)
- Microsoft Learn β Use managed identities in an Azure Batch account or pool
- Microsoft Learn β Configure customer-managed keys for your Azure Batch account
- β
tf-mod-azure-batch/SCOPE.md(composite scope contract)
π "Infrastructure as Code should be standardized, consistent, and secure."