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/spring-deployment-guide.md
1# Deployment Guide: Spring Boot to Azure Container Apps23## Phase 1: Create Container Apps Environment45**Bash:**6```bash7#!/bin/bash8set -euo pipefail9az group create --name spring-rg --location eastus10az monitor log-analytics workspace create --resource-group spring-rg --workspace-name spring-logs --location eastus11LOG_ID=$(az monitor log-analytics workspace show --resource-group spring-rg --workspace-name spring-logs --query customerId -o tsv)12LOG_KEY=$(az monitor log-analytics workspace get-shared-keys --resource-group spring-rg --workspace-name spring-logs --query primarySharedKey -o tsv)13az containerapp env create --name spring-env --resource-group spring-rg --location eastus --logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY"14```1516**PowerShell:**17```powershell18az group create --name spring-rg --location eastus19az monitor log-analytics workspace create --resource-group spring-rg --workspace-name spring-logs --location eastus20$LOG_ID = az monitor log-analytics workspace show --resource-group spring-rg --workspace-name spring-logs --query customerId -o tsv21$LOG_KEY = az monitor log-analytics workspace get-shared-keys --resource-group spring-rg --workspace-name spring-logs --query primarySharedKey -o tsv22az containerapp env create --name spring-env --resource-group spring-rg --location eastus --logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY"23```2425## Phase 2: Configure Logging2627**Update application.properties:**28```properties29logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n30```3132Configure diagnostic settings: Azure Monitor Log Analytics (recommended), Event Hubs, or third-party solutions.3334## Phase 3: Containerize Application3536**Dockerfile:**37```dockerfile38FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu39WORKDIR /app40COPY target/*.jar app.jar41EXPOSE 808042ENTRYPOINT ["java", "-jar", "app.jar"]43```4445**Build and push (Bash):**46```bash47ACR_NAME="${ACR_NAME:-<acr>}"48az acr create --name "$ACR_NAME" --resource-group spring-rg --sku Basic --location eastus49az acr login --name "$ACR_NAME"50docker build -t "${ACR_NAME}.azurecr.io/spring-app:v1.0" .51docker push "${ACR_NAME}.azurecr.io/spring-app:v1.0"52```5354**Build and push (PowerShell):**55```powershell56$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" }57az acr create --name "$ACR_NAME" --resource-group spring-rg --sku Basic --location eastus58az acr login --name "$ACR_NAME"59docker build -t "${ACR_NAME}.azurecr.io/spring-app:v1.0" .60docker push "${ACR_NAME}.azurecr.io/spring-app:v1.0"61```6263## Phase 4: Configure Storage (if needed)6465**Azure Files for persistent storage (Bash):**66```bash67STORAGE_ACCOUNT="${STORAGE_ACCOUNT:-<storage-account>}"68az storage account create --name "$STORAGE_ACCOUNT" --resource-group spring-rg --location eastus --sku Standard_LRS69STORAGE_KEY=$(az storage account keys list --account-name "$STORAGE_ACCOUNT" --resource-group spring-rg --query "[0].value" -o tsv)70az storage share create --name spring-data --account-name "$STORAGE_ACCOUNT" --account-key "$STORAGE_KEY"71az containerapp env storage set --name spring-env --resource-group spring-rg --storage-name spring-storage \72--azure-file-account-name "$STORAGE_ACCOUNT" --azure-file-account-key "$STORAGE_KEY" \73--azure-file-share-name spring-data --access-mode ReadWrite74```7576**Azure Files for persistent storage (PowerShell):**77```powershell78$STORAGE_ACCOUNT = if ($env:STORAGE_ACCOUNT) { $env:STORAGE_ACCOUNT } else { "<storage-account>" }79az storage account create --name "$STORAGE_ACCOUNT" --resource-group spring-rg --location eastus --sku Standard_LRS80$STORAGE_KEY = az storage account keys list --account-name "$STORAGE_ACCOUNT" --resource-group spring-rg --query "[0].value" -o tsv81az storage share create --name spring-data --account-name "$STORAGE_ACCOUNT" --account-key "$STORAGE_KEY"82az containerapp env storage set --name spring-env --resource-group spring-rg --storage-name spring-storage `83--azure-file-account-name "$STORAGE_ACCOUNT" --azure-file-account-key "$STORAGE_KEY" `84--azure-file-share-name spring-data --access-mode ReadWrite85```8687## Phase 5: Migrate Secrets to Key Vault8889> **Security Note:** Avoid passing secrets via `--value` on the command line (leaks via shell history). Use `--file` with a protected temp file or prompt securely instead.9091**Bash:**92```bash93ACR_NAME="${ACR_NAME:-<acr>}" # From Phase 394KEY_VAULT="${KEY_VAULT:-<keyvault>}"95az keyvault create --name "$KEY_VAULT" --resource-group spring-rg --location eastus96IDENTITY_ID=$(az identity create --name spring-id --resource-group spring-rg --location eastus --query id -o tsv)97PRINCIPAL_ID=$(az identity show --ids "$IDENTITY_ID" --query principalId -o tsv)98az keyvault set-policy --name "$KEY_VAULT" --object-id "$PRINCIPAL_ID" --secret-permissions get list99100# Secure approach using temp file101SECRET_FILE=$(mktemp)102trap 'shred -u "$SECRET_FILE" 2>/dev/null || rm -f "$SECRET_FILE"' EXIT103read -s -p "Enter database password: " DB_PASSWORD104echo -n "$DB_PASSWORD" > "$SECRET_FILE"105az keyvault secret set --vault-name "$KEY_VAULT" --name db-password --file "$SECRET_FILE"106107ACR_ID=$(az acr show --name "$ACR_NAME" --query id -o tsv)108az role assignment create --assignee "$PRINCIPAL_ID" --role AcrPull --scope "$ACR_ID"109```110111**PowerShell:**112```powershell113$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" } # From Phase 3114$KEY_VAULT = if ($env:KEY_VAULT) { $env:KEY_VAULT } else { "<keyvault>" }115az keyvault create --name "$KEY_VAULT" --resource-group spring-rg --location eastus116$IDENTITY_ID = az identity create --name spring-id --resource-group spring-rg --location eastus --query id -o tsv117$PRINCIPAL_ID = az identity show --ids "$IDENTITY_ID" --query principalId -o tsv118az keyvault set-policy --name "$KEY_VAULT" --object-id "$PRINCIPAL_ID" --secret-permissions get list119120# Secure approach using temp file121$SECRET_FILE = [System.IO.Path]::GetTempFileName()122try {123$SecurePassword = Read-Host "Enter database password" -AsSecureString124$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)125try {126$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)127[System.IO.File]::WriteAllText($SECRET_FILE, $PlainPassword)128az keyvault secret set --vault-name "$KEY_VAULT" --name db-password --file "$SECRET_FILE"129} finally {130[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)131}132} finally {133Remove-Item $SECRET_FILE -Force -ErrorAction SilentlyContinue134}135136$ACR_ID = az acr show --name "$ACR_NAME" --query id -o tsv137az role assignment create --assignee "$PRINCIPAL_ID" --role AcrPull --scope "$ACR_ID"138```139140## Phase 6: Deploy Container App141142**Bash:**143```bash144ACR_NAME="${ACR_NAME:-<acr>}" # From Phase 3145KEY_VAULT="${KEY_VAULT:-<keyvault>}" # From Phase 5146IDENTITY_ID="${IDENTITY_ID:?Set IDENTITY_ID from Phase 5}"147SECRET_URI=$(az keyvault secret show --vault-name "$KEY_VAULT" --name db-password --query id -o tsv)148az containerapp create --name spring-app --resource-group spring-rg --environment spring-env \149--image "${ACR_NAME}.azurecr.io/spring-app:v1.0" --target-port 8080 --ingress external \150--cpu 2.0 --memory 4Gi --min-replicas 2 --max-replicas 10 \151--user-assigned "$IDENTITY_ID" --registry-identity "$IDENTITY_ID" --registry-server "${ACR_NAME}.azurecr.io" \152--secrets db-password=keyvaultref:"${SECRET_URI}",identityref:"${IDENTITY_ID}" \153--env-vars SPRING_DATASOURCE_PASSWORD=secretref:db-password SPRING_PROFILES_ACTIVE=prod154```155156**PowerShell:**157```powershell158$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" } # From Phase 3159$KEY_VAULT = if ($env:KEY_VAULT) { $env:KEY_VAULT } else { "<keyvault>" } # From Phase 5160$IDENTITY_ID = if ($env:IDENTITY_ID) { $env:IDENTITY_ID } else { throw "Set IDENTITY_ID from Phase 5" }161$SECRET_URI = az keyvault secret show --vault-name "$KEY_VAULT" --name db-password --query id -o tsv162az containerapp create --name spring-app --resource-group spring-rg --environment spring-env `163--image "${ACR_NAME}.azurecr.io/spring-app:v1.0" --target-port 8080 --ingress external `164--cpu 2.0 --memory 4Gi --min-replicas 2 --max-replicas 10 `165--user-assigned "$IDENTITY_ID" --registry-identity "$IDENTITY_ID" --registry-server "${ACR_NAME}.azurecr.io" `166--secrets db-password=keyvaultref:"${SECRET_URI}",identityref:"${IDENTITY_ID}" `167--env-vars SPRING_DATASOURCE_PASSWORD=secretref:db-password SPRING_PROFILES_ACTIVE=prod168```169170**With storage mount:** Export the app configuration, add volumeMounts, and update:171172**Bash:**173```bash174az containerapp show --name spring-app --resource-group spring-rg -o yaml > app.yaml175# Edit app.yaml: add volumeMounts under containers[0] and volumes at template level176az containerapp update --name spring-app --resource-group spring-rg --yaml app.yaml177```178179**PowerShell:**180```powershell181az containerapp show --name spring-app --resource-group spring-rg -o yaml | Out-File -Encoding utf8 app.yaml182# Edit app.yaml: add volumeMounts under containers[0] and volumes at template level183az containerapp update --name spring-app --resource-group spring-rg --yaml app.yaml184```185186**Health Probes** (recommended for Spring Boot apps): Export configuration, add probes, and update:187188**Bash:**189```bash190az containerapp show --name spring-app --resource-group spring-rg -o yaml > app.yaml191# Edit app.yaml: add probes under containers[0]192# probes:193# - type: Startup194# httpGet:195# path: /actuator/health196# port: 8080197# failureThreshold: 30198# periodSeconds: 2199# - type: Liveness200# httpGet:201# path: /actuator/health/liveness202# port: 8080203# - type: Readiness204# httpGet:205# path: /actuator/health/readiness206# port: 8080207az containerapp update --name spring-app --resource-group spring-rg --yaml app.yaml208```209210**PowerShell:**211```powershell212az containerapp show --name spring-app --resource-group spring-rg -o yaml | Out-File -Encoding utf8 app.yaml213# Edit app.yaml: add probes under containers[0]214# probes:215# - type: Startup216# httpGet:217# path: /actuator/health218# port: 8080219# failureThreshold: 30220# periodSeconds: 2221# - type: Liveness222# httpGet:223# path: /actuator/health/liveness224# port: 8080225# - type: Readiness226# httpGet:227# path: /actuator/health/readiness228# port: 8080229az containerapp update --name spring-app --resource-group spring-rg --yaml app.yaml230```231232## Phase 7: Validation233234**Bash:**235```bash236FQDN=$(az containerapp show --name spring-app --resource-group spring-rg --query properties.configuration.ingress.fqdn -o tsv)237echo "Application URL: https://${FQDN}"238curl "https://${FQDN}/actuator/health"239az containerapp logs show --name spring-app --resource-group spring-rg --tail 50240```241242**PowerShell:**243```powershell244$FQDN = az containerapp show --name spring-app --resource-group spring-rg --query properties.configuration.ingress.fqdn -o tsv245Write-Host "Application URL: https://${FQDN}"246Invoke-WebRequest "https://${FQDN}/actuator/health"247az containerapp logs show --name spring-app --resource-group spring-rg --tail 50248```249250## Phase 8: Post-Migration Optimization251252### Add Spring Cloud Config Server253254**Bash:**255```bash256az containerapp env java-component config-server-for-spring create \257--environment spring-env --resource-group spring-rg --name config-server \258--min-replicas 1 --max-replicas 1 \259--configuration spring.cloud.config.server.git.uri=https://github.com/your-org/config-repo260az containerapp update --name spring-app --resource-group spring-rg --bind config-server261```262263**PowerShell:**264```powershell265az containerapp env java-component config-server-for-spring create `266--environment spring-env --resource-group spring-rg --name config-server `267--min-replicas 1 --max-replicas 1 `268--configuration spring.cloud.config.server.git.uri=https://github.com/your-org/config-repo269az containerapp update --name spring-app --resource-group spring-rg --bind config-server270```271272### Add Eureka Service Registry273274**Bash:**275```bash276az containerapp env java-component eureka-server-for-spring create \277--environment spring-env --resource-group spring-rg --name eureka-server \278--min-replicas 1 --max-replicas 1279az containerapp update --name spring-app --resource-group spring-rg --bind eureka-server280```281282**PowerShell:**283```powershell284az containerapp env java-component eureka-server-for-spring create `285--environment spring-env --resource-group spring-rg --name eureka-server `286--min-replicas 1 --max-replicas 1287az containerapp update --name spring-app --resource-group spring-rg --bind eureka-server288```289290**Add dependency (pom.xml):**291```xml292<dependency>293<groupId>org.springframework.cloud</groupId>294<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>295</dependency>296```297298### Add Spring Cloud Gateway299300**Bash:**301```bash302az containerapp create --name spring-gateway --resource-group spring-rg --environment spring-env \303--image "${ACR_NAME}.azurecr.io/gateway:v1.0" --target-port 8080 --ingress external \304--cpu 1.0 --memory 2Gi --min-replicas 1 --max-replicas 5 \305--bind eureka-server config-server306```307308**PowerShell:**309```powershell310az containerapp create --name spring-gateway --resource-group spring-rg --environment spring-env `311--image "${ACR_NAME}.azurecr.io/gateway:v1.0" --target-port 8080 --ingress external `312--cpu 1.0 --memory 2Gi --min-replicas 1 --max-replicas 5 `313--bind eureka-server config-server314```315316### Add Spring Boot Admin317318**Bash:**319```bash320az containerapp env java-component admin-for-spring create \321--environment spring-env --resource-group spring-rg --name admin-server \322--min-replicas 1 --max-replicas 1323az containerapp update --name spring-app --resource-group spring-rg --bind admin-server324```325326**PowerShell:**327```powershell328az containerapp env java-component admin-for-spring create `329--environment spring-env --resource-group spring-rg --name admin-server `330--min-replicas 1 --max-replicas 1331az containerapp update --name spring-app --resource-group spring-rg --bind admin-server332```333334## Troubleshooting335336| Issue | Solution |337|-------|----------|338| Image pull fails | Verify ACR role: `az role assignment list --assignee $PRINCIPAL_ID --scope $ACR_ID` |339| App won't start | Check logs: `az containerapp logs show --name spring-app -g spring-rg --tail 100` |340| Health check fails | Verify port 8080 matches `server.port` in application.properties |341| Secrets not accessible | Check Key Vault policy: `az keyvault show --name $KEY_VAULT --query properties.accessPolicies` |342| Storage mount fails | Verify storage configuration: `az containerapp env storage list --name spring-env -g spring-rg` |343| High memory usage | Reduce max heap: add `--env-vars JAVA_OPTS="-Xmx2g"` to container app |344345## CI/CD Integration346347**GitHub Actions example:**348```yaml349- name: Build and push to ACR350run: |351az acr build --registry ${{ secrets.ACR_NAME }} --image spring-app:${{ github.sha }} .352- name: Deploy to Container Apps353run: |354az containerapp update --name spring-app -g spring-rg --image ${{ secrets.ACR_NAME }}.azurecr.io/spring-app:${{ github.sha }}355```356357**Azure Pipelines example:**358```yaml359- task: AzureCLI@2360inputs:361azureSubscription: 'AzureConnection'362scriptType: 'bash'363scriptLocation: 'inlineScript'364inlineScript: |365az acr build --registry $(ACR_NAME) --image spring-app:$(Build.BuildId) .366az containerapp update --name spring-app -g spring-rg --image $(ACR_NAME).azurecr.io/spring-app:$(Build.BuildId)367```368