Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
GitHub Copilot for Azure plugin providing Azure service management and development assistance inside Claude Code and IDEs.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/services/container-apps/deployment-guide.md
1# Kubernetes to Azure Container Apps - Deployment Guide23## Prerequisites45Azure CLI 2.53.0+, kubectl, Docker, Azure subscription, ACR, Key Vault, Log Analytics67## Phase 1: Export Kubernetes Resources89```bash10kubectl get deployments,services,ingress -n <namespace> -o wide11kubectl get configmaps,secrets -n <namespace>12```1314### Export Script1516```bash17#!/bin/bash18set -euo pipefail19NAMESPACE="${K8S_NAMESPACE:-<namespace>}"20OUTPUT_DIR="${OUTPUT_DIR:-k8s-export}"21mkdir -p "$OUTPUT_DIR"22kubectl get deploy,svc,ingress,configmap,secret -n "$NAMESPACE" -o yaml > "$OUTPUT_DIR/all-resources.yaml"23for deploy in $(kubectl get deploy -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}'); do24kubectl get deployment "$deploy" -n "$NAMESPACE" -o yaml > "$OUTPUT_DIR/deploy-${deploy}.yaml"25done26```2728## Phase 2: Assess Compatibility2930Load [assessment-guide.md](assessment-guide.md). Check: StatefulSets, DaemonSets, CRDs, resource limits (>4 vCPU/>8 GiB), PVCs, NetworkPolicies.3132## Phase 3: Migrate Images3334> ⚠️ **Warning:** Azure Container Apps only runs **linux/amd64** images. If you build on Apple Silicon or another ARM host, use `docker buildx build --platform linux/amd64` or `az acr build` (which builds amd64 by default). Verify with `docker inspect <image> --format '{{.Architecture}}'`.3536### Bash3738```bash39#!/bin/bash40set -euo pipefail41ACR_NAME="${ACR_NAME:-<acr>}"42SOURCE_REGISTRY="${SOURCE_REGISTRY:-<registry>}"43az acr login --name "$ACR_NAME"44az acr import --name "$ACR_NAME" --source "${SOURCE_REGISTRY}/app:v1.0" --image app:v1.045```4647### PowerShell4849```powershell50$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" }51$SOURCE_REGISTRY = if ($env:SOURCE_REGISTRY) { $env:SOURCE_REGISTRY } else { "<registry>" }52az acr login --name $ACR_NAME53az acr import --name $ACR_NAME --source "${SOURCE_REGISTRY}/app:v1.0" --image app:v1.054```5556## Phase 4: Infrastructure5758### Bash5960```bash61az group create --name myapp-rg --location eastus62az monitor log-analytics workspace create --resource-group myapp-rg --workspace-name myapp-logs --location eastus63LOG_ID=$(az monitor log-analytics workspace show --resource-group myapp-rg --workspace-name myapp-logs --query customerId -o tsv)64LOG_KEY=$(az monitor log-analytics workspace get-shared-keys --resource-group myapp-rg --workspace-name myapp-logs --query primarySharedKey -o tsv)65az containerapp env create --name myapp-env --resource-group myapp-rg --location eastus --logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY"66```6768### PowerShell6970```powershell71az group create --name myapp-rg --location eastus72az monitor log-analytics workspace create --resource-group myapp-rg --workspace-name myapp-logs --location eastus73$LOG_ID = az monitor log-analytics workspace show --resource-group myapp-rg --workspace-name myapp-logs --query customerId -o tsv74$LOG_KEY = az monitor log-analytics workspace get-shared-keys --resource-group myapp-rg --workspace-name myapp-logs --query primarySharedKey -o tsv75az containerapp env create --name myapp-env --resource-group myapp-rg --location eastus --logs-workspace-id $LOG_ID --logs-workspace-key $LOG_KEY76```7778**VNet:** For VNet integration, create VNet first, get subnet ID, then use `--infrastructure-subnet-resource-id` with env create.7980## Phase 5: Secrets8182> **Tip**: Prefer piping decoded secret values directly to `az keyvault secret set --value` to avoid writing sensitive data to disk.8384### Bash8586```bash87ACR_NAME="${ACR_NAME:-<acr>}"8889# Create Key Vault and migrate secrets (pipe directly — no temp file)90az keyvault create --name myapp-kv --resource-group myapp-rg --location eastus91az keyvault secret set --vault-name myapp-kv --name password --value "$(kubectl get secret mysecret -n <namespace> -o jsonpath='{.data.password}' | base64 -d)"9293# Create managed identity and grant permissions94az identity create --name myapp-id --resource-group myapp-rg --location eastus95IDENTITY_ID=$(az identity show --name myapp-id --resource-group myapp-rg --query id -o tsv)96PRINCIPAL_ID=$(az identity show --name myapp-id --resource-group myapp-rg --query principalId -o tsv)97KV_ID=$(az keyvault show --name myapp-kv --resource-group myapp-rg --query id -o tsv)98az role assignment create --assignee "$PRINCIPAL_ID" --role "Key Vault Secrets User" --scope "$KV_ID"99ACR_ID=$(az acr show --name "$ACR_NAME" --query id -o tsv)100az role assignment create --assignee "$PRINCIPAL_ID" --role AcrPull --scope "$ACR_ID"101```102103### PowerShell104105```powershell106$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" }107108# Create Key Vault and migrate secrets (pass directly — no temp file, no BOM issues)109az keyvault create --name myapp-kv --resource-group myapp-rg --location eastus110$secretValue = kubectl get secret mysecret -n <namespace> -o jsonpath='{.data.password}'111$decodedSecretValue = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secretValue))112az keyvault secret set --vault-name myapp-kv --name password --value $decodedSecretValue113Remove-Variable -Name decodedSecretValue, secretValue -ErrorAction SilentlyContinue114115# Create managed identity and grant permissions116az identity create --name myapp-id --resource-group myapp-rg --location eastus117$IDENTITY_ID = az identity show --name myapp-id --resource-group myapp-rg --query id -o tsv118$PRINCIPAL_ID = az identity show --name myapp-id --resource-group myapp-rg --query principalId -o tsv119$KV_ID = az keyvault show --name myapp-kv --resource-group myapp-rg --query id -o tsv120az role assignment create --assignee $PRINCIPAL_ID --role "Key Vault Secrets User" --scope $KV_ID121$ACR_ID = az acr show --name $ACR_NAME --query id -o tsv122az role assignment create --assignee $PRINCIPAL_ID --role AcrPull --scope $ACR_ID123```124125## Phase 6: Deploy126127> ⚠️ **Warning: Service Discovery Changes** — In Kubernetes, pods reach other services by short DNS name (e.g., `http://order-service:3001`). In Container Apps, internal services use HTTPS FQDNs (e.g., `https://order-service.internal.<env-domain>`). **Audit application code** for hardcoded Kubernetes hostnames/ports in HTTP clients, proxy logic, or connection strings — these must be replaced with env-var-driven URLs that point to the Container Apps internal FQDN.128129**Mapping:** `spec.containers[].image` → `template.containers[].image`; `spec.containers[].ports[].containerPort` → `ingress.targetPort`; `spec.replicas` → `scale.minReplicas`. Service types: ClusterIP → `external: false`; LoadBalancer/NodePort → `external: true`.130131```bash132# Get Key Vault secret URI and deploy133SECRET_URI=$(az keyvault secret show --vault-name myapp-kv --name password --query id -o tsv)134az containerapp create --name my-app --resource-group myapp-rg --environment myapp-env \135--image $ACR_NAME.azurecr.io/app:v1.0 --target-port 8080 --ingress external \136--cpu 1.0 --memory 2Gi --min-replicas 2 --max-replicas 10 \137--user-assigned $IDENTITY_ID --registry-identity $IDENTITY_ID --registry-server $ACR_NAME.azurecr.io \138--secrets password=keyvaultref:$SECRET_URI,identityref:$IDENTITY_ID \139--env-vars ENV=prod DB_PASSWORD=secretref:password \140--scale-rule-name http --scale-rule-type http --scale-rule-http-concurrency 80141```142143```powershell144$SECRET_URI = az keyvault secret show --vault-name myapp-kv --name password --query id -o tsv145az containerapp create --name my-app --resource-group myapp-rg --environment myapp-env `146--image "$ACR_NAME.azurecr.io/app:v1.0" --target-port 8080 --ingress external `147--cpu 1.0 --memory 2Gi --min-replicas 2 --max-replicas 10 `148--user-assigned $IDENTITY_ID --registry-identity $IDENTITY_ID --registry-server "$ACR_NAME.azurecr.io" `149--secrets "password=keyvaultref:$SECRET_URI,identityref:$IDENTITY_ID" `150--env-vars ENV=prod DB_PASSWORD=secretref:password `151--scale-rule-name http --scale-rule-type http --scale-rule-http-concurrency 80152```153154## Phase 7: Validation155156```bash157# Get app FQDN and test158FQDN=$(az containerapp show --name my-app --resource-group myapp-rg --query properties.configuration.ingress.fqdn -o tsv)159echo "App URL: https://$FQDN"160curl https://$FQDN/health161162# View logs163az containerapp logs show --name my-app --resource-group myapp-rg --follow164```165166```powershell167$FQDN = az containerapp show --name my-app --resource-group myapp-rg --query properties.configuration.ingress.fqdn -o tsv168Write-Host "App URL: https://$FQDN"169Invoke-WebRequest -Uri "https://$FQDN/health" -Method Head170171# View logs172az containerapp logs show --name my-app --resource-group myapp-rg --follow173```174175## Troubleshooting176177| Issue | Solution |178|-------|----------|179| Image pull | Verify ACR: `az acr check-health --name $ACR_NAME`; check AcrPull role |180| Wrong architecture | ACA requires linux/amd64. Check: `docker inspect <image> --format '{{.Architecture}}'`. Rebuild with `--platform linux/amd64` |181| Port mismatch | Verify `targetPort` matches app port |182| OOM | Increase memory limit (up to 4 vCPU / 8 GiB max per container) |183| DNS | Retrieve FQDN: `az containerapp show --name <app> -g <rg> --query properties.configuration.ingress.fqdn -o tsv` |184| NSG blocking provisioning | If VNet-integrated, ensure NSG does **not** have a custom DenyAllInbound at low priority — it blocks Azure Load Balancer probes and VNet-internal traffic. The default rules (65000-65500) handle deny. Add explicit AllowAzureLoadBalancer rule |185| SecretRef not found | `--env-vars KEY=secretref:name` requires `--secrets name=value` (or keyvaultref) in the **same** `az containerapp create` command |186| ARM deployment locks | If a Bicep deployment is stuck with Container Apps InProgress, run `az deployment group cancel -g <rg> -n <deployment>` before attempting CLI updates or deletes |187| Service-to-service timeout | Kubernetes DNS names (`http://svc:port`) don't work in ACA. Ensure app code reads `ORDER_SERVICE_URL` (or equivalent) env var pointing to the internal FQDN |188