Terraform (azurerm) as the alternative to Bicep

When Terraform's azurerm provider is the better fit than Bicep and what changes for an agent — state instead of ARM-side deployments, provider authentication (OIDC from GitHub Actions with use_oidc, managed identity, CLI), the azurerm v5 defaults that break old configurations (no automatic resource-provider registration, subscription_id required), Entra and storage settings, and known-bad configurations with their symptoms.

markdownpatternv1MFS5-8126stage: buildtrust: officialtrust: fieldservice: armservice: terraforminterface: iacTerraform azurermTerraform vs Bicepazurerm v5resource_provider_registrations noneMissingSubscriptionRegistration TerraformARM_USE_OIDC use_oidc GitHub ActionsARM_CLIENT_ID ARM_TENANT_ID ARM_SUBSCRIPTION_IDsubscription_id required plan applyfeatures block requireduse_msiuse_clistorage_use_azureadazurerm federated credentials sequential 3.40Terraform state Azure storage

Created 21 Oct 2026 · Modified 23 Oct 2026 · 1 version

When to pick which

PickWhen
BicepAzure only. You want no state file to host and lock, day-one support for new API versions (once Bicep has the types), what-if, and deployment stacks for deletion and deny settings
Terraform azurermYou already run Terraform, manage several clouds or SaaS providers in one plan, or want plan/apply with state-based drift detection

Both call the same Resource Manager APIs, so the ARM error catalogues (reference/arm/errors.md, reference/arm/deployment-errors.md) and the RBAC and policy documents apply unchanged. Terraform adds its own state and provider-configuration failures.

Provider configuration that works (azurerm 5.x)

  • features {} block: required.
  • subscription_id: required for plan and apply (not for validate). Set it, or ARM_SUBSCRIPTION_ID.
  • Resource providers: since v5 the default resource_provider_registrations is none, so nothing is registered for you. Either set resource_provider_registrations = "legacy" (the v4 behaviour) or list the providers in resource_providers_to_register, or register them once with az provider register.
  • Authentication:
  • From GitHub Actions: use_oidc = true (or ARM_USE_OIDC=true) with ARM_CLIENT_ID and ARM_TENANT_ID. The provider reads ACTIONS_ID_TOKEN_REQUEST_URL / _TOKEN itself, and the job needs permissions: id-token: write. The federated credential rules are the same as for azure/login (reference/entra/federated-credential-errors.md).
  • On Azure compute: use_msi = true.
  • Locally: the Azure CLI (use_cli, on by default).
  • storage_use_azuread = true makes the provider use Entra ID instead of shared keys for blob and queue data. Leave it at the default false and the provider needs storage account keys, which fails when shared-key access is disabled.

Known-bad

ConfigurationSymptom
A v4 configuration upgraded to v5 unchangedMissingSubscriptionRegistration for providers v4 used to register
No subscription_id anywhereplan fails; validate passes, which hides it until CI
use_oidc without id-token: write in the workflowThe provider can't get a GitHub token (the same failure as azure/login)
Several federated credentials on one user-assigned identity, provider older than 3.40.0409 conflicts: older versions created them concurrently
Role assignments with name unset and principals recreatedDuplicate or conflicting assignments (RoleAssignmentExists, RoleAssignmentUpdateNotPermitted): the same causes as in Bicep (reference/rbac/errors.md)
Shared-key access disabled on the storage account, storage_use_azuread falseData-plane operations on containers or queues fail with authorization errors (storage family)

The provider's own pages are the reference for resource arguments. This hub only covers what an agent gets wrong when moving between the two tools.

Read this document as markdown — the same bytes an agent receives.