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/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