Azure Container Apps Provider

Deploy preview environments to Azure Container Apps.

How it works

Prerequisites

  1. An Azure subscription.
  2. An Azure resource group.
  3. An Azure Container Registry (ACR).
  4. An Azure Container Apps managed environment.
  5. An Azure service principal with the right permissions.

Step 1 — Create a resource group (if needed)

az group create --name previewops-rg --location eastus

Step 2 — Create an Azure Container Registry

az acr create \
  --resource-group previewops-rg \
  --name mypreviewops \
  --sku Basic

Note: the registry name must be globally unique. The acrServer value will be mypreviewops.azurecr.io.

Step 3 — Create a Container Apps managed environment

az containerapp env create \
  --name previewops-env \
  --resource-group previewops-rg \
  --location eastus

This also creates a Log Analytics workspace automatically (~$2–5/month depending on log volume).

Step 4 — Create a service principal and collect credentials

You need four values: AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID, and AZURE_SUBSCRIPTION_ID.

# Get your subscription ID
SUBSCRIPTION_ID=$(az account show --query id -o tsv)

# Create the service principal
az ad sp create-for-rbac \
  --name previewops-sp \
  --role Contributor \
  --scopes /subscriptions/$SUBSCRIPTION_ID/resourceGroups/previewops-rg \
  --output json

Note: The --sdk-auth flag was deprecated in Azure CLI 2.47 and removed in later versions. Use --output json instead.

This outputs JSON with appId, password, tenant, and displayName. Map fields to credentials:

Credential JSON field
AZURE_CLIENT_ID appId
AZURE_CLIENT_SECRET password
AZURE_TENANT_ID tenant
AZURE_SUBSCRIPTION_ID $SUBSCRIPTION_ID from above

Option B — Azure Portal (manual)

AZURE_SUBSCRIPTION_ID

  1. Go to portal.azure.com.
  2. Search Subscriptions in the top search bar.
  3. Click your subscription — copy the Subscription ID from the overview page.

AZURE_TENANT_ID

  1. Search Microsoft Entra ID in the top search bar.
  2. On the overview page, copy the Tenant ID.

AZURE_CLIENT_ID and AZURE_CLIENT_SECRET (service principal)

  1. In Microsoft Entra ID, go to App registrationsNew registration.
  2. Enter a name (e.g. previewops-sp), leave other defaults, click Register.
  3. On the app overview page, copy Application (client) ID → this is AZURE_CLIENT_ID.
  4. Go to Certificates & secretsNew client secret.
  5. Set a description and expiry, click Add.
  6. Copy the Value immediately — it is only shown once → this is AZURE_CLIENT_SECRET.

Grant the service principal Contributor access on your resource group

  1. Go to Resource groups → select your resource group.
  2. Access control (IAM)AddAdd role assignment.
  3. Role: Contributor → Next.
  4. Members: User, group, or service principal → search for the app name → select it → Review + assign.

Step 5 — Grant ACR push permission to the service principal

Option A — Azure CLI

ACR_ID=$(az acr show --name mypreviewops --resource-group previewops-rg --query id -o tsv)

az role assignment create \
  --assignee <clientId-from-step-4> \
  --role AcrPush \
  --scope $ACR_ID

Option B — Azure Portal

  1. Go to Container registries → select your registry.
  2. Access control (IAM)AddAdd role assignment.
  3. Role: AcrPush → Next.
  4. Members: User, group, or service principal → search for the app name from step 4 → select it → Review + assign.

Step 6 — Enable managed identity on the environment and grant AcrPull

Previewops configures Container Apps to pull images using the managed environment's system-assigned identity. You must enable it and grant it AcrPull on your registry.

Option A — Azure CLI

Note: The containerapp env identity command group is in preview and requires the containerapp CLI extension. If the command fails with a provider registration error despite Microsoft.App showing as Registered, use Option B (Portal) instead — it is more reliable for this step.

# Enable system-assigned identity on the managed environment
az containerapp env identity assign \
  --name previewops-env \
  --resource-group previewops-rg \
  --system-assigned

# Get the identity's principal ID
ENV_PRINCIPAL_ID=$(az containerapp env show \
  --name previewops-env \
  --resource-group previewops-rg \
  --query identity.principalId -o tsv)

# Grant AcrPull to the managed environment identity
az role assignment create \
  --assignee $ENV_PRINCIPAL_ID \
  --role AcrPull \
  --scope $ACR_ID

Enable system-assigned identity:

  1. Go to portal.azure.com → search Container Apps Environments → select your environment.
  2. Left sidebar → SettingsIdentity.
  3. System assigned tab → toggle Status to OnSave.
  4. Copy the Object (principal) ID that appears.

Grant AcrPull to the identity:

  1. Go to Container registries → select your registry.
  2. Left sidebar → Access control (IAM)AddAdd role assignment.
  3. Role: search for AcrPullNext.
  4. Members: select Managed identity+ Select members.
  5. Managed identity dropdown → select Container Apps Environment → pick your environment → Select.
  6. Review + assign.

Without this step, Container Apps will fail to pull the built image at deploy time.

Step 7 — Configure the repo

provider: azure-container-apps
providerConfig:
  resourceGroup: previewops-rg           # required — resource group name (step 1)
  managedEnvironment: previewops-env     # required — Container Apps environment name (step 3)
  acrServer: mypreviewops.azurecr.io     # required — ACR login server (step 2, format: {name}.azurecr.io)
  registryName: mypreviewops             # required — ACR name without the .azurecr.io suffix
  location: eastus                       # optional (default: eastus); must match the environment's location

Complete example with all options:

# .previewops.yaml
provider: azure-container-apps
providerConfig:
  resourceGroup: previewops-rg           # Azure resource group containing all resources (step 1)
  managedEnvironment: previewops-env     # Container Apps managed environment name (step 3)
  acrServer: mypreviewops.azurecr.io     # ACR login server — format: {registryName}.azurecr.io (step 2)
  registryName: mypreviewops             # ACR name without .azurecr.io suffix (step 2)
  location: eastus                       # Azure region (default: eastus)

# Common options — all optional
concurrency: 3                           # max simultaneous active previews per repo
ttlHours: 24                             # auto-delete after N hours (default: 24)
port: 3000                               # optional — auto-detected from your Dockerfile EXPOSE; falls back to 8080
memory: 512Mi                            # container memory (default: 512Mi)
cpu: 1                                   # container vCPU (default: 1)
dockerfile: Dockerfile                   # Dockerfile path relative to repo root (default: Dockerfile)
buildContext: .                          # Docker build context (default: .)
env:
  NODE_ENV: preview                      # inject env vars into the preview container

Step 8 — Verify

Comment /validate-previewops on any open PR. The bot acquires a management API token using the service principal and reports success.

Cost notes

Resource Cost
Container Apps ~$0.01–$0.05/hour per preview (one replica per PR, running until the PR is closed)
Managed environment ~$0 infrastructure, charged per replica
Log Analytics workspace ~$2–5/month
ACR Basic ~$5/month

Total baseline cost: ~$7–10/month for the environment and registry.

Troubleshooting

Error Fix
AZURE_CLIENT_ID / AZURE_CLIENT_SECRET are required Set all 4 Azure secrets
azure-container-apps providerConfig.resourceGroup is required Add resourceGroup to providerConfig
AuthenticationError Service principal may be expired — re-create it
The resource group was not found Check the resourceGroup name and location match what you created
ACR build fails Ensure the service principal has AcrPush on the registry (step 5)

Known limitations

Private repositories: ACR Tasks clones the repository over HTTPS using the raw sourceLocation URL. GitHub private repos require an OAuth token embedded in the URL, which is not currently supported. The Azure Container Apps provider works with public repositories only.

If your repository is private, use an SSH-based provider instead (Hetzner Cloud, DigitalOcean, or Docker SSH) — these providers build on your own server using SSH key authentication, which supports private repos without embedding credentials in URLs.