Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Assess and migrate workloads from AWS, GCP, or other clouds to Azure services.
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