Azure Container Apps Provider
Deploy preview environments to Azure Container Apps.
How it works
- Build: uses Azure Container Registry (ACR) Tasks to build the Docker image remotely. ACR clones the repo and builds without requiring local Docker.
- Deploy: creates or updates an Azure Container App in your managed environment.
- Delete: deletes the Container App (the managed environment persists).
- List: lists all Container Apps in the resource group tagged with Previewops metadata.
Prerequisites
- An Azure subscription.
- An Azure resource group.
- An Azure Container Registry (ACR).
- An Azure Container Apps managed environment.
- 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.
Option A — Azure CLI (recommended)
# 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-authflag was deprecated in Azure CLI 2.47 and removed in later versions. Use--output jsoninstead.
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
- Go to portal.azure.com.
- Search Subscriptions in the top search bar.
- Click your subscription — copy the Subscription ID from the overview page.
AZURE_TENANT_ID
- Search Microsoft Entra ID in the top search bar.
- On the overview page, copy the Tenant ID.
AZURE_CLIENT_ID and AZURE_CLIENT_SECRET (service principal)
- In Microsoft Entra ID, go to App registrations → New registration.
- Enter a name (e.g.
previewops-sp), leave other defaults, click Register. - On the app overview page, copy Application (client) ID → this is
AZURE_CLIENT_ID. - Go to Certificates & secrets → New client secret.
- Set a description and expiry, click Add.
- Copy the Value immediately — it is only shown once → this is
AZURE_CLIENT_SECRET.
Grant the service principal Contributor access on your resource group
- Go to Resource groups → select your resource group.
- Access control (IAM) → Add → Add role assignment.
- Role: Contributor → Next.
- 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
- Go to Container registries → select your registry.
- Access control (IAM) → Add → Add role assignment.
- Role: AcrPush → Next.
- 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 identitycommand group is in preview and requires thecontainerappCLI extension. If the command fails with a provider registration error despiteMicrosoft.Appshowing asRegistered, 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
Option B — Azure Portal (recommended if CLI fails)
Enable system-assigned identity:
- Go to portal.azure.com → search Container Apps Environments → select your environment.
- Left sidebar → Settings → Identity.
- System assigned tab → toggle Status to On → Save.
- Copy the Object (principal) ID that appears.
Grant AcrPull to the identity:
- Go to Container registries → select your registry.
- Left sidebar → Access control (IAM) → Add → Add role assignment.
- Role: search for AcrPull → Next.
- Members: select Managed identity → + Select members.
- Managed identity dropdown → select Container Apps Environment → pick your environment → Select.
- 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.