Blog
March 12, 2026

Terraforming Your Azure: A Practical Guide to Migrating from Bicep to Terraform

You might find Bicep serves you well for a while and then starts to show its limits as your environment grows more complex. Here's our migration guide to help teams who have overgown Bicep.

Daniel Caduri
~ min read
~0 min read

Infrastructure as Code (IaC) has become the default way to manage modern cloud environments because it lets you define infrastructure using the same disciplines you apply to application code: version control, code review, and automated delivery.

Instead of clicking through portals, you describe the desired state of your infrastructure and let your tooling handle the rest. In the Azure world, two names come up in almost every IaC discussion: Bicep and Terraform.

Both tools can reliably provision and manage Azure resources, but they take very different approaches and come with different long‑term implications. Bicep is tightly aligned with Azure and excels when you are committed to staying within the Azure ecosystem, while Terraform focuses on a provider‑agnostic model and a broad ecosystem that spans clouds and SaaS platforms. The “right” choice is less about which tool is objectively better and more about your roadmap: multi‑cloud vs Azure‑only, how you structure teams, and how you want to enforce governance and consistency.

If you are already invested in Bicep today, you might find that it serves you well for a while and then starts to show its limits as your environment grows more complex, more regulated, or more multi‑cloud. That is usually the point where teams start evaluating a shift toward Terraform to gain better portability, a stronger ecosystem, and more flexible state and lifecycle management

Why teams outgrow Bicep

Bicep is excellent when you are “all‑in” on Azure and want a thin, declarative layer over ARM without extra tooling.

Key advantages of Bicep:

  • Native Azure experience: Day‑O support for new Azure services and tight integration with Azure Portal, CLI, and RBAC.
  • No explicit state files: Bicep relies on ARM as the source of truth, so you do not manage .tfstate files or backends.
  • Simple onboarding: If you can deploy to Azure, you can deploy with Bicep using existing Azure credentials and pipelines.
  • Good authoring experience: Strong type checking, resource scaffolding, and IntelliSense in VS Code.

Key limitations that cause friction:

  • Azure‑only: You cannot manage AWS, GCP, on‑prem, or SaaS providers; this becomes a hard stop in multi‑cloud or hybrid scenarios.
  • Ecosystem and tooling: Fewer community modules, patterns, and integrations compared to Terraform’s large ecosystem and registry.
  • Portability: Architectures codified in Bicep are strongly coupled to ARM; reusing patterns outside Azure is difficult.
  • State‑level refactoring: Without a first‑class state layer, complex refactors (splitting stacks, moving resources between projects) are more constrained.

Several engineers who tried moving in the opposite direction (Terraform → Bicep) report missing Terraform’s ecosystem, refactoring tools, and provider breadth, even if they liked Bicep’s Azure focus.

Why move to Terraform

Terraform’s value starts to dominate once you look beyond a single Azure subscription or need more advanced lifecycle control.

Key advantages of Terraform:

  • Multi‑cloud by design: Single workflow and language to manage Azure, AWS, GCP, Kubernetes, on‑prem, and SaaS (GitHub, Datadog, etc.).
  • Huge provider ecosystem: Thousands of providers and modules, including the official azurerm provider and community Azure modules.
  • Strong state model: Import existing resources, move items between states, and perform complex refactors with less risk.
  • Mature practices and tooling: Widespread patterns for testing, policy as code, private registries, and CI/CD integration.

Main trade‑offs:

  • State management overhead: You must design and maintain remote state backends, locking, and access controls.
  • Slight lag for new Azure features: Bicep often supports new Azure resources before the azurerm provider catches up.
  • Learning curve for HCL and patterns: Teams used to Bicep and ARM need to adapt to Terraform’s syntax, module system, and workflow.

A common pattern today: keep Bicep for small, Azure‑only workloads, but standardize on Terraform for anything cross‑cloud, cross‑team, or long‑lived.

Critical considerations before migrating

Before writing any Terraform, step back and decide what you are actually migrating and how you want to operate long‑term.

1. Define scope and goals

  • Are you migrating all Bicep, or only core “platform” components (networking, identity, shared services)?
  • Are you aiming for multi‑cloud, or “Azure‑first but Terraform‑based”?
  • Do you want a 1:1 translation, or a redesign into reusable modules and layers?

Your answers determine whether a direct translation is acceptable, or whether you should treat this as an opportunity to restructure.

2. Inventory existing Bicep deployments

Collect:

  • All Bicep templates, modules, and parameter files (including those embedded in pipelines).
  • The resource groups, subscriptions, and environments (dev/test/prod) each template touches.
  • Shared resources (e.g., VNets, Key Vaults, identity) referenced by multiple Bicep deployments.

This inventory becomes the basis for your migration plan and helps avoid accidentally recreating shared resources.

3. Decide how you will handle state

State is usually the biggest conceptual shift coming from Bicep.

Key design decisions:

  • Backend: Azure Storage with state locking via Azure Blob / Cosmos, or Terraform Cloud/Enterprise, or another backend.
  • Granularity: One state per environment? Per application? Per layer (networking, platform, workload)?
  • Access and locking: Who can read/write state, and how do you enforce that only CI pipelines (not local laptops) can update it?

A good rule of thumb is to keep blast radius small: avoid a single, huge state per subscription unless your environment is very simple.

4. Module and folder structure

Terraform migration is the perfect time to introduce a maintainable structure:

  • Core modules: Network, identity, monitoring, storage, app hosting, data platforms.
  • Environment definitions: Small “stack” configurations that compose modules with per‑env variables.
  • Separation of concerns: One stack per lifecycle boundary (e.g., shared network vs application resources).

Many teams moving from Bicep to Terraform regret copy‑pasting resource blocks instead of designing a proper module layout upfront.

5. CI/CD and governance model

  • Map existing az deployment or Bicep tasks to Terraform commands (init, plan, apply).​
  • Decide where plans run (Azure DevOps, GitHub Actions, GitLab, etc.) and who must approve them.
  • Integrate policies (e.g., Azure Policy, OPA/Conftest) and security scanning into the pipeline.

This ensures you do not simply replace az deployment with terraform apply from someone’s laptop.

Step‑by‑step migration approach

You generally have two choices:

  • Big‑bang: Freeze Bicep, move everything to Terraform at once, then cut over.
  • Incremental: Gradually bring resources under Terraform control while Bicep still exists for other parts.

For anything non‑trivial, an incremental strategy is safer.

Step 1 – Freeze Bicep changes

  • Put a temporary change freeze on the Bicep‑managed environments you plan to migrate, except for critical fixes.
  • Communicate that all new infrastructure should be created via Terraform once the new pipelines are live.

This reduces drift between Bicep definitions and actual Azure resources during the migration window.

Step 2 – Export and understand the current state

You need a complete view of what is actually deployed:

  • Use Azure Resource Graph / az CLI to export current resources for each scope (sub, resource group, tags).
  • Optionally use tools like “Azure Export for Terraform” or similar exporters to generate initial HCL or mapping hints.
  • Identify dependencies: shared VNets, route tables, Key Vaults, managed identities, role assignments, etc.

The goal is not perfect HCL yet, but a reliable map of your current architecture.

Step 3 – Design Terraform state layout and modules

Based on your inventory:

  • Define which resources live in which state file (e.g., “shared‑network‑prod”, “platform‑services‑prod”, “app‑foo‑prod”).
  • Create Terraform modules that encapsulate the Bicep patterns you already use (e.g., a standard App Service stack, a standard AKS cluster).
  • Decide on input variables that align with your existing naming conventions and tags.

At this stage, you can already write “empty” Terraform stacks that reference modules but have no imported resources yet.

Step 4 – Author equivalent Terraform configurations

For each Bicep template:

  • Map Bicep parameters to Terraform variables and locals.
  • Map Bicep resources (e.g., Microsoft.Web/sites) to the corresponding azurerm_* resources.​
  • Recreate outputs where they are consumed by other systems or pipelines.

Do not run apply yet against production resources; instead, aim for configurations that match what is already in Azure.

Step 5 – Import existing Azure resources into Terraform state

This is the most delicate step: you want Terraform to adopt existing resources without recreating them.

  • Configure the remote backend and run terraform init.
  • For each resource, run terraform import using the correct ID format from Azure.
  • Use terraform plan to verify that Terraform sees “no changes” for imported resources.

If the plan shows differences, adjust your Terraform configuration (names, sku, tags, or defaults) until the plan is effectively a no‑op.

Step 6 – Replace Bicep deployments in pipelines

Once Terraform configuration is importing cleanly and plans are zero‑change:

  • Modify CI/CD pipelines to replace Bicep deployment steps with Terraform plan/apply stages.​
  • Set pipelines to run plan on every PR and apply only on approved merges.
  • Remove or comment out Bicep deployment steps for the migrated stacks, but keep templates archived for rollback if needed.

Run a few cycles of non‑disruptive changes (e.g., tag updates) to validate your new workflow end‑to‑end.​

Step 7 – Decommission Bicep for that scope

After multiple successful Terraform changes:

  • Retire the Bicep templates or mark them explicitly as deprecated.
  • Update documentation and runbooks so on‑call and support engineers know Terraform is now the source of truth.
  • Enable additional guardrails (e.g., disable portal‑level changes where feasible, rely on Terraform for drift correction).

Repeat the same process for the next application or environment until all critical Bicep stacks are moved.

Common pitfalls and how to avoid them

  • Treating migration as pure syntax conversion: This often recreates legacy mistakes in Terraform; use migration as a chance to modularize and standardize.
  • Oversized states: Huge, monolithic state files make plans slow and risky; design smaller, logical stacks instead.
  • Forgetting shared resources: If multiple Bicep templates use the same VNet or Key Vault, importing those into multiple states leads to conflicts.
  • Mixing manual changes with Terraform: Portal or CLI edits after migration create drift; agree that Terraform is the only change path and enforce it via policy and process.
  • Not planning for secrets: Decide early how you handle access keys, connection strings, and certificates (Key Vault + Terraform data sources, or external secret managers).

A small pilot migration (one app in one environment) is a good way to surface these issues before you roll out Terraform everywhere.

Where SGCode fits into a Bicep → Terraform journey

Even with a solid plan, the most time‑consuming part of moving from Bicep to Terraform is usually codifying and importing all the existing Azure resources into clean HCL and state files.

SGCode (StackGuardian’s Infra2Code module for Terraform/OpenTofu) is designed to automate exactly that kind of brownfield codification work:

  • It discovers existing resources across Azure subscriptions and groups them by actual application and dependency structure instead of dumping flat resource lists.​
  • It generates Terraform or OpenTofu configurations that follow your organization’s naming conventions, module patterns, and file layout, rather than generic templates.
  • It automatically creates and manages Terraform state, avoiding manual, per‑resource import commands that can take 10–60 minutes each in large environments.​
  • It plugs into existing StackGuardian workflows, enabling immediate drift detection, policy enforcement, and self‑service after migration.

If you already know you want to standardize on Terraform but are blocked by the sheer volume of existing Azure infrastructure defined in or alongside Bicep, SGCode can turn that manual discovery and import phase into an automated, repeatable process, letting your engineers focus on designing good Terraform modules and pipelines instead of one‑off conversion tasks.

Share article