Skip to content

How It Works

The metadata_host trick

The hashicorp/azurerm Terraform provider has a metadata_host configuration field. When set, the provider calls https://{metadata_host}/metadata/endpoints to discover Azure service URLs instead of using its built-in cloud profiles.

azemu serves this endpoint and returns URLs pointing back to itself. All subsequent ARM calls, token requests, and data-plane calls stay local.

flowchart LR
    TF[Terraform CLI] -->|metadata_host| AZ[azemu :4567]
    AZ -->|returns self-URLs| TF
    TF -->|all ARM calls| AZ2[azemu :4566]
    AZ2 -->|responses| TF

Your Terraform configuration points at azemu like this:

provider "azurerm" {
  features {}
  metadata_host           = "localhost:4567"
  skip_provider_registration = false
}

The same configuration works with OpenTofu. Both tools use the azurerm provider and both honour metadata_host, so azemu does not care which one you run. See License & Forking for the licensing difference between Terraform and OpenTofu.

Why HTTPS is required

The azurerm provider inspects the resourceManager URL from the metadata response. If it uses http:// instead of https://, the provider classifies the environment as Azure Stack and refuses to connect.

azemu serves both ports over HTTPS using a self-signed ECDSA P-256 certificate to avoid this classification. The certificate is generated at first start and persisted at .azemu/cert-bundle.pem. You export SSL_CERT_FILE pointing at that path so the Go TLS stack trusts it.

Ports

Port Protocol Purpose
4566 HTTPS ARM API, data plane
4567 HTTPS Metadata service, OAuth2, OIDC
4568 HTTP Health check (container probes, no TLS)

Request flow

flowchart TD
    dev[Developer / CI] --> tf[Terraform CLI]
    tf -->|HTTPS :4567| meta[Metadata Service]
    tf -->|HTTPS :4567| auth[Auth Service\nOAuth2 / OIDC / JWKS]
    tf -->|HTTPS :4566| arm[ARM API]
    arm --> rg[Resource Groups]
    arm --> net[Networking\nVNet, Subnet, NSG, ...]
    arm --> storage[Storage\ndelegated to Azurite]
    arm --> kv[Key Vault]
    arm --> more[more resources]
    arm --> state[State Store\nmemory or file]

On startup, azemu generates (or reloads) a self-signed certificate and starts both HTTPS servers. The state store holds every resource in memory by default; pass --persist to write it to disk so state survives a restart.