Azure Account Setup

Setting up Azure for tsdevstack deployments. Each environment requires its own subscription for isolation.

Warning

App Service quota required before first deploy. New subscriptions often have zero quota for Basic-tier VMs. You must request a quota increase before running infra:deploy, or it will fail with Current Limit (Basic VMs): 0. See App Service Quota below.

Prerequisites

  • Azure account with portal access
  • One Azure subscription per environment (dev, staging, prod)
  • App Service plan quota in your target region (Basic/B-series)

Register Resource Providers

Azure subscriptions do not have all resource providers enabled by default. You must register every provider the framework uses before running cloud:init. This is a one-time operation per subscription.

How to Register

  1. Go to the Azure Portal
  2. Search for "Subscriptions" and click on your subscription. If you have more than one, check the Subscription ID: it must be the one in your credentials file, otherwise the providers get registered in the wrong place
  3. In the left sidebar under Settings, click Resource providers
  4. For each provider listed below:
    • Type the provider name in the search box
    • Select it from the list
    • Click Register at the top
    • The status progress message may disappear from the list. Click the bell icon (notifications) in the top-right navbar to track progress and verify registration succeeded.
    • Wait until the status shows Registered (usually under a minute)

Required Providers

Register all 12 providers:

ProviderUsed For
Microsoft.AppContainer Apps (backend services)
Microsoft.CacheAzure Cache for Redis
Microsoft.CdnAzure Front Door (CDN / load balancer)
Microsoft.ContainerRegistryContainer Registry (Docker images)
Microsoft.DBforPostgreSQLPostgreSQL Flexible Server
microsoft.insightsMonitor Diagnostic Settings
Microsoft.KeyVaultKey Vault (secrets management)
Microsoft.ManagedIdentityManaged Identities for containers
Microsoft.NetworkVirtual Networks, subnets, NSGs
Microsoft.OperationalInsightsLog Analytics workspace
Microsoft.StorageStorage Accounts (Terraform state, SPA hosting)
Microsoft.WebApp Service Plans (Kong, Next.js)
Warning

The Microsoft.Web provider must be registered before you can request App Service quota (next section). The quota page will be empty without it.

App Service Quota

The framework deploys Kong on a dedicated App Service Plan. If your project includes Next.js apps, they share a second App Service Plan. New subscriptions often have zero quota for these VM families, so you must request an increase before the first deployment.

The default SKU is B1. If you override it to an S-series SKU in infrastructure.json, you need quota for that family instead (or both, if you mix SKUs).

  1. Go to the Azure Portal
  2. Search for "Quotas" > click My quotas

Find and Request

  1. Filter: Service: App Service, Location: your deployment region
  2. Request quota for each VM family you need:
SKU in infrastructure.jsonQuota to Request
B1, B2, B3 (default)B1/B2/B3 VMs
S1, S2, S3S1/S2/S3 VMs
P1v3, P2v3, P3v3P1v3/P2v3/P3v3 VMs
  1. Select the row (e.g., "B1 VMs", showing "0 of 0") > click Request increase
  2. Set the new limit based on your needs — 1 for Kong, plus 1 if your project includes Next.js apps (all Next.js apps share one plan)
  3. Submit and wait for approval (usually 1-4 hours, often minutes)

If the self-service option is not available, create a support request: Help + support > + Create a support request > Issue type: "Service and subscription limits (quotas)" > Quota type: "App Service"

Azure Managed Redis Access

The framework runs Redis on Azure Managed Redis (default size Balanced_B0, set with redis.tier in infrastructure.json). Azure doesn't let every subscription create it. New pay-as-you-go subscriptions in particular can be blocked in a region until you ask, whatever size you pick. The deploy then fails with:

Creation of Azure Managed Redis with the SKU Balanced_B0 is not supported for your subscription in East US 2.
Please choose a different SKU or region. If you need access to this SKU, please contact support.

There is no API to check this ahead of time, so on a new subscription request access before your first deploy:

  1. Help + support > + Create a support request
  2. Issue type: "Service and subscription limits (quotas)"
  3. Pick the Redis quota type the form offers (Azure Managed Redis or Azure Cache for Redis)
  4. Ask for Azure Managed Redis with the size you'll use (Balanced_B0 by default) in your deployment region. If a deploy already failed, add the CorrelationID from the error.

Everything else in a deploy is created before it stops at Redis, so once access is granted, run infra:deploy again and it continues where it left off.

Step 1: Create App Registration

  1. Go to Azure Portal > search "Microsoft Entra ID"
  2. Under Manage, click App registrations > + New registration
  3. Name: {projectName}-{env} (e.g., myapp-dev)
  4. Supported account types: "Single tenant" (Accounts in this organizational directory only)
  5. Redirect URI: Leave blank
  6. Click Register

From the Overview page, copy:

  • Application (client) ID > this is clientId
  • Directory (tenant) ID > this is tenantId

Step 2: Create Client Secret

  1. Click on your App Registration from the list to open it
  2. In the left sidebar under Manage, click Certificates & secrets
  3. Click + New client secret
  4. Description: {projectName}-{env}-key (e.g., myapp-dev-key)
  5. Expires: 24 months
  6. Click Add
  7. Two fields appear side by side: Secret ID and Value. Copy the Value field (the long string) — this is your clientSecret. It is only shown once; if you navigate away, you cannot retrieve it.

Step 3: Get Subscription ID

  1. Search for "Subscriptions" in the portal
  2. Click your subscription
  3. Copy the Subscription ID

Step 4: Create Resource Group

Naming is required

The resource group must be named exactly {projectName}-{env}-rg. The framework derives this name from your project name and environment — there is no way to override it. If the name doesn't match, all commands will fail.

  1. Search for "Resource groups" > + Create
  2. Subscription: Select from Step 3
  3. Resource group: {projectName}-{env}-rg (e.g., tsdevstack-dev-rg)
  4. Region: Choose your region (e.g., East US 2). Avoid East US — PostgreSQL Flexible Server is often restricted there.
  5. Click Review + create > Create

Step 5: Grant Permissions

The Service Principal needs three roles on the resource group.

Go to the Resource Group > Access control (IAM) > assign each role:

Role 1: Contributor

  • + Add > Add role assignment
  • Tab: Privileged administrator roles > select Contributor
  • Select members > search for tsdevstack-dev > assign

Role 2: Key Vault Secrets Officer

  • + Add > Add role assignment
  • Tab: Job function roles > search Key Vault Secrets Officer
  • Select members > search for tsdevstack-dev > assign

Role 3: User Access Administrator (constrained)

  • + Add > Add role assignment
  • Tab: Privileged administrator roles > select User Access Administrator
  • Select members > search for tsdevstack-dev
  • On the Conditions step: click Select roles and principals
  • Select Constrain roles template
  • Click + Select roles > search and add each:
    • Key Vault Secrets Officer
    • Key Vault Secrets User
    • AcrPull
    • Storage Blob Data Contributor
  • Save and assign
RolePurpose
ContributorCreate and manage resources (Key Vault, Container Apps, databases, etc.)
Key Vault Secrets OfficerRead, write, and delete secrets in Key Vault
User Access Administrator (constrained)Assign only Key Vault, AcrPull, and Storage roles to Container App Managed Identities

All roles above are scoped to the resource group. Provider registration (done earlier) is a separate subscription-level operation.

Step 6: Save Credentials

Create .tsdevstack/.credentials.azure.json:

{
  "dev": {
    "clientId": "<Application (client) ID from Step 1>",
    "clientSecret": "<Secret Value from Step 2>",
    "tenantId": "<Directory (tenant) ID from Step 1>",
    "subscriptionId": "<Subscription ID from Step 3>",
    "location": "eastus2"
  }
}

Add more environments as needed. Each environment must use a different subscriptionId (enforced by framework).

Step 7: Initialize

npx tsdevstack cloud:init --azure

This will:

  1. Validate each environment has a unique subscription
  2. Verify resource providers are registered (skips already-registered providers)
  3. Create Key Vault with RBAC authorization enabled
  4. Assign Key Vault Secrets Officer role to the SP
  5. Test connection to Key Vault
  6. Update .tsdevstack/config.json with cloud.provider: "azure"

Safe to run multiple times — all operations are idempotent.

Secret Naming

Azure Key Vault only allows alphanumeric characters and hyphens. The framework auto-transforms underscores:

  • DATABASE_URL > stored as DATABASE-URL
  • JWT_PRIVATE_KEY_CURRENT > stored as JWT-PRIVATE-KEY-CURRENT

Your code always uses underscores — the transformation is transparent.

Environment Isolation

Each environment must use a separate subscription (unique subscriptionId). The framework validates this during cloud:init.

Troubleshooting

"Forbidden" or "Access Denied"

  1. Check the SP has all three roles on the resource group (Step 5)
  2. Verify credentials match the App Registration
  3. Verify subscriptionId matches the subscription containing the resource group

"does not have authorization to perform action ... register/action"

Resource providers are not registered on the subscription. Go to Subscriptions > your subscription > Resource providers and register all providers listed in Register Resource Providers.

"Key Vault not found"

Run npx tsdevstack cloud:init --azure to create it.

"Credentials file not found"

Ensure the file exists at .tsdevstack/.credentials.azure.json.

"ServerNameAlreadyExists" on the PostgreSQL server

PostgreSQL Flexible Server names are global DNS names, and the default {project}-{env}-postgres is already taken somewhere in Azure (another project with the same name, or a server left in a subscription you deleted). Set a different name for that environment with database.serverName in .tsdevstack/infrastructure.json, then deploy again. See Custom server name (Azure).

"Creation of Azure Managed Redis with the SKU ... is not supported for your subscription"

Your subscription isn't allowed to create Azure Managed Redis in that region yet. Request access through a support request, see Azure Managed Redis Access. Choosing a bigger size doesn't help: the block usually applies to the product, not to one size.