Installation
Install with CLI
Recommended
gh skills-hub install azure-cloud-migrate Don't have the extension? Run gh extension install samueltauil/skills-hub first.
Download and extract to your repository:
.github/skills/azure-cloud-migrate/ Extract the ZIP to .github/skills/ in your repo. The folder name must match azure-cloud-migrate for Copilot to auto-discover it.
Skill Files (31)
SKILL.md 4.2 KB
---
name: azure-cloud-migrate
description: "Assess and migrate cross-cloud workloads to Azure with reports and code conversion. Supports Lambda→Functions, Beanstalk/Heroku/App Engine→App Service, Fargate/Kubernetes/Cloud Run/Spring Boot→Container Apps. WHEN: migrate Lambda to Functions, AWS to Azure, migrate Beanstalk, migrate Heroku, migrate App Engine, Cloud Run migration, Fargate to ACA, ECS/Kubernetes/GKE/EKS to Container Apps, Spring Boot to Container Apps, cross-cloud migration."
license: MIT
metadata:
author: Microsoft
version: "1.2.1"
---
# Azure Cloud Migrate
> This skill handles **assessment and code migration** of existing cloud workloads to Azure.
## Rules
1. Follow phases sequentially — do not skip
2. Generate assessment before any code migration
3. Load the scenario reference and follow its rules
4. Use `mcp_azure_mcp_get_azure_bestpractices` and `mcp_azure_mcp_documentation` MCP tools
5. Use the latest supported runtime for the target service
6. Destructive actions require `ask_user` — [functions global-rules](references/services/functions/global-rules.md) | [app-service global-rules](references/services/app-service/global-rules.md)
7. **Report progress to user** — During long-running operations (deployments, image pushes), provide resource-level status updates so the user is never left waiting without feedback — see [workflow-details.md](references/workflow-details.md)
8. **Audit service discovery in app code** — Kubernetes DNS names (e.g., `http://order-service:3001`) do not resolve in Container Apps. During assessment, scan source code for hardcoded hostnames/ports in HTTP clients and flag them for env-var-driven URL injection
## Migration Scenarios
| Source | Target | Reference |
|--------|--------|-----------|
| AWS Lambda | Azure Functions | [lambda-to-functions.md](references/services/functions/lambda-to-functions.md) ([assessment](references/services/functions/assessment.md), [code-migration](references/services/functions/code-migration.md)) |
| AWS Elastic Beanstalk | Azure App Service | [beanstalk-to-app-service.md](references/services/app-service/beanstalk-to-app-service.md) |
| Heroku | Azure App Service | [heroku-to-app-service.md](references/services/app-service/heroku-to-app-service.md) |
| Google App Engine | Azure App Service | [app-engine-to-app-service.md](references/services/app-service/app-engine-to-app-service.md) |
| AWS Fargate (ECS) | Azure Container Apps | [fargate-to-container-apps.md](references/services/container-apps/fargate-to-container-apps.md) ([assessment](references/services/container-apps/fargate-assessment-guide.md), [deployment](references/services/container-apps/fargate-deployment-guide.md)) |
| Kubernetes (GKE/EKS/Self-hosted) | Azure Container Apps | [k8s-to-container-apps.md](references/services/container-apps/k8s-to-container-apps.md) |
| GCP Cloud Run | Azure Container Apps | [cloudrun-to-container-apps.md](references/services/container-apps/cloudrun-to-container-apps.md) |
| Spring Boot (Azure Spring Apps/VMs) | Azure Container Apps | [spring-apps-to-aca.md](references/services/container-apps/spring-apps-to-aca.md) |
> No matching scenario? Use `mcp_azure_mcp_documentation` and `mcp_azure_mcp_get_azure_bestpractices` tools.
## Output Directory
All output goes to `<workspace-root-basename>-azure/` at workspace root, where `<workspace-root-basename>` is the name of the top-level workspace directory itself (NOT a subdirectory within it). Never modify the source directory.
## Steps
1. **Create** `<workspace-root-basename>-azure/` at workspace root
2. **Assess** — Analyze source, map services, generate report using the scenario-specific assessment guide → [functions assessment](references/services/functions/assessment.md) | [app-service assessment](references/services/app-service/assessment.md)
3. **Migrate** — Convert code/config using the scenario-specific migration guide → [functions code-migration](references/services/functions/code-migration.md) | [app-service code-migration](references/services/app-service/code-migration.md)
4. **Ask User** — "Migration complete. Test locally or deploy to Azure?"
5. **Hand off** to azure-prepare for infrastructure, testing, and deployment
Track progress in `migration-status.md` — see [workflow-details.md](references/workflow-details.md).
references/services/app-service/
app-engine-to-app-service.md 7.1 KB
# Google App Engine to Azure App Service Migration
Detailed guidance for migrating Google App Engine applications to Azure App Service.
## Service Mapping
| GCP Service | Azure Equivalent |
|-------------|------------------|
| App Engine (Standard) | Azure App Service (Standard/Premium plan) |
| App Engine (Flex) | Azure App Service (Premium v3) or Container Apps |
| App Engine Services | App Service apps (one per service) |
| `app.yaml` | `azure.yaml` + Bicep |
| Datastore / Firestore | Azure Cosmos DB |
| Cloud SQL (PostgreSQL) | Azure Database for PostgreSQL Flexible Server |
| Cloud SQL (MySQL) | Azure Database for MySQL Flexible Server |
| Cloud Storage | Azure Blob Storage |
| Cloud Tasks | Azure Service Bus / Durable Functions |
| Cloud Pub/Sub | Azure Service Bus / Event Grid |
| Memcache / Memorystore | Azure Cache for Redis |
| Cloud Scheduler | Azure Functions Timer trigger |
| Cloud CDN | Azure Front Door / Azure CDN |
| Cloud DNS | Azure DNS |
| Cloud Logging | Application Insights / Azure Monitor |
| Cloud Monitoring | Azure Monitor Metrics |
| Cloud Trace | Application Insights (distributed tracing) |
| Cloud IAM | Managed Identity + Azure RBAC |
| Cloud Build | GitHub Actions / Azure DevOps |
| Traffic Splitting | Deployment Slots (weighted routing) |
| Service Versions | Deployment Slots |
| Cloud KMS | Azure Key Vault |
| Secret Manager | Azure Key Vault |
| Cloud Endpoints | Azure API Management |
| Identity-Aware Proxy | Azure Front Door + Entra ID auth |
| VPC Connector | VNet Integration |
## Standard vs Flex → App Service Plan Mapping
| App Engine Tier | App Service Equivalent | Notes |
|-----------------|------------------------|-------|
| Standard (F1 instance) | Free (F1) | Dev/test only |
| Standard (B1/B2) | Basic (B1/B2) | Low-traffic apps |
| Standard (auto-scaling) | Standard (S1-S3) | Auto-scale, slots |
| Flex (custom runtime) | Premium v3 (P1v3-P3v3) | Custom containers |
| Flex (high-memory) | Premium v3 (P3v3) | Up to 32 GB RAM |
## `app.yaml` → `azure.yaml` + Bicep
### App Engine Configuration Mapping
| `app.yaml` Field | Azure Equivalent | Implementation |
|-------------------|------------------|----------------|
| `runtime: python312` | Runtime stack: Python 3.12 | Bicep `siteConfig.linuxFxVersion` |
| `instance_class: F2` | App Service Plan SKU | Bicep `sku.name` |
| `automatic_scaling` | Autoscale settings | Bicep `Microsoft.Insights/autoscalesettings` |
| `env_variables` | App Settings | Bicep `siteConfig.appSettings` |
| `handlers` (URL routing) | App Service path mappings / Front Door | Route rules |
| `entrypoint` | Startup command | `az webapp config set --startup-file` |
| `vpc_access_connector` | VNet Integration | Bicep `virtualNetworkSubnetId` |
| `inbound_services: [warmup]` | Always On | Bicep `siteConfig.alwaysOn: true` |
### Example: `app.yaml` → Bicep
```yaml
# app.yaml (GCP)
runtime: python312
instance_class: F2
automatic_scaling:
min_instances: 1
max_instances: 10
env_variables:
DATABASE_URL: "postgres://..."
```
```bicep
// Bicep equivalent
resource appServicePlan 'Microsoft.Web/serverfarms@2023-12-01' = {
sku: { name: 'S2', capacity: 1 }
properties: { reserved: true }
}
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
properties: {
siteConfig: {
linuxFxVersion: 'PYTHON|3.12'
alwaysOn: true
appSettings: [
{ name: 'PGHOST', value: postgresServer.properties.fullyQualifiedDomainName }
]
}
}
}
```
## Datastore / Firestore → Cosmos DB
| Datastore Feature | Cosmos DB Equivalent |
|-------------------|----------------------|
| Entity / Document | Item |
| Kind / Collection | Container |
| Namespace | Database |
| Key / ID | Partition key + ID |
| Ancestor queries | Hierarchical partition keys |
| Eventual consistency | Session or Eventual consistency |
| Strong consistency | Strong consistency |
| `ndb` / `google.cloud.datastore` | `@azure/cosmos` SDK |
> 💡 **Tip:** Use Cosmos DB for NoSQL API for the closest mapping from Datastore. Use Cosmos DB for MongoDB API if the app already uses MongoDB-compatible patterns.
## Cloud Tasks → Service Bus / Durable Functions
| Cloud Tasks Feature | Azure Equivalent |
|--------------------|------------------|
| HTTP target tasks | Service Bus + Azure Functions |
| App Engine target tasks | Service Bus + WebJobs |
| Task scheduling (delayed) | Service Bus scheduled messages |
| Task retry / dead-letter | Service Bus retry + dead-letter queue |
| Rate limiting | Service Bus message throttling |
| Task orchestration | Durable Functions (fan-out/fan-in) |
## Traffic Splitting → Deployment Slots
| App Engine Feature | Azure Equivalent |
|--------------------|------------------|
| Version traffic splitting (%) | Slot traffic routing (%) |
| Canary deployment | Slot with low traffic % |
| A/B testing | Slot traffic routing |
| Version rollback | Slot swap (revert) |
| Gradual rollout | Increase slot traffic % incrementally |
| Warmup requests | Slot warm-up (pre-swap) |
```bicep
// Configure traffic routing: 90% production, 10% staging
resource siteConfig 'Microsoft.Web/sites/config@2023-12-01' = {
name: 'web'
properties: {
experiments: {
rampUpRules: [{
actionHostName: '${webApp.name}-staging.azurewebsites.net'
reroutePercentage: 10
name: 'staging'
}]
}
}
}
```
## Memcache → Azure Cache for Redis
| Memcache Feature | Azure Cache for Redis |
|------------------|----------------------|
| `memcache.get()` / `set()` | `redis.get()` / `redis.set()` |
| Shared memcache | Azure Cache for Redis (Basic) |
| Dedicated memcache | Azure Cache for Redis (Standard/Premium) |
| Session store | Redis session store |
| Cache expiry | Redis TTL |
## Environment Variables Migration
| Source | Target | Notes |
|--------|--------|-------|
| `app.yaml` env_variables | App Settings | Non-sensitive config |
| Secret Manager refs | Key Vault references | `@Microsoft.KeyVault(SecretUri=...)` |
| Runtime env (auto-injected) | App Settings | Map GAE-specific vars |
| `GAE_APPLICATION` | `WEBSITE_SITE_NAME` | Auto-injected by App Service |
| `PORT` | `PORT` | Auto-injected by App Service |
| `GOOGLE_CLOUD_PROJECT` | (no equivalent) | GCP project IDs identify a runtime environment; Azure subscription IDs are a tooling/billing context, not a runtime equivalent. Use App Service `WEBSITE_RESOURCE_GROUP` or rely on `DefaultAzureCredential` to discover the subscription at runtime if needed. |
## CI/CD Migration
| Cloud Build / gcloud | Azure Equivalent |
|---------------------|------------------|
| `gcloud app deploy` | `az webapp deploy` or `azd deploy` |
| `cloudbuild.yaml` | `.github/workflows/deploy.yml` |
| Cloud Build triggers | GitHub Actions triggers |
| Artifact Registry | Azure Container Registry |
| `gcloud app versions` | Deployment slots |
## Reference Links
- [GCP to Azure services comparison](https://learn.microsoft.com/en-us/azure/architecture/gcp-professional/)
- [App Service overview](https://learn.microsoft.com/en-us/azure/app-service/overview)
- [Cosmos DB migration options](https://learn.microsoft.com/en-us/azure/cosmos-db/migration-choices)
- [App Service deployment slots](https://learn.microsoft.com/en-us/azure/app-service/deploy-staging-slots)
assessment.md 5.2 KB
# Assessment Phase
Generate a migration assessment report before any code changes.
## Prerequisites
- Workspace contains source platform project files (Procfile, app.yaml, .ebextensions, etc.)
- Prompt user to upload relevant files if not present
## Assessment Steps
1. **Identify Application** — Determine app type (web, API, worker), runtime, and framework
2. **Map Platform Services** — Map source services to Azure equivalents (see scenario-specific references)
3. **Map Properties** — Map compute config (instance size, scaling) to App Service Plan properties
4. **Check Dependencies** — List 3rd-party libraries and verify Azure compatibility
5. **Analyze Code** — Check for platform-specific APIs, SDKs, or patterns that need migration
6. **Map Data Services** — Identify database and storage migration paths
7. **Map Deployment** — Identify equivalent Azure deployment strategies (azd, GitHub Actions, Bicep)
8. **Review CI/CD** — Check pipeline compatibility with Azure DevOps or GitHub Actions
9. **Map Monitoring** — Map observability stack → Application Insights / Azure Monitor
## Code Preview
During assessment, show a **sneak peek** of key configuration changes:
- Startup command / Dockerfile adjustments
- App Settings mapping
- Database connection string migration (to managed identity)
This helps the user understand the migration scope before committing.
## Architecture Diagrams
Generate two diagrams:
1. **Current State** — Source platform architecture with services and integrations
2. **Target State** — Azure architecture showing equivalent App Service structure
## Assessment Report Format
> ⚠️ **MANDATORY**: Use these exact section headings in every assessment report. Do NOT rename, reorder, or omit sections.
The report MUST be saved as `migration-assessment-report.md` inside the output directory (`<source-folder>-azure/`).
```markdown
# Migration Assessment Report
## 1. Executive Summary
| Property | Value |
|----------|-------|
| **Application Name** | <name> |
| **Application Type** | Web App / API / Worker |
| **Source Platform** | <Heroku / Elastic Beanstalk / App Engine> |
| **Source Runtime** | <runtime and version> |
| **Target Platform** | Azure App Service |
| **Target Runtime** | <runtime and version> |
| **Migration Readiness** | <High / Medium / Low> |
| **Estimated Effort** | <Low / Medium / High> |
| **Assessment Date** | <date> |
## 2. Application Inventory
| # | Component | Runtime | Type | Instances | Description |
|---|-----------|---------|------|-----------|-------------|
| 1 | | | Web / Worker / Cron | | |
## 3. Service Mapping
| Source Service | Azure Equivalent | Migration Complexity | Notes |
|----------------|------------------|----------------------|-------|
| | | | |
## 4. Compute & Scaling Mapping
| # | Source Config | Value | Azure Equivalent | Azure Value | Notes |
|---|-------------|-------|------------------|-------------|-------|
| 1 | Instance type | | App Service Plan SKU | | |
| 2 | Auto-scaling | | App Service Autoscale | | |
| 3 | Health check | | Health Check feature | | |
## 5. Dependencies Analysis
| # | Package/Library | Version | Platform-Specific? | Azure Equivalent | Compatible? | Notes |
|---|----------------|---------|---------------------|------------------|-------------|-------|
| 1 | | | | | | |
## 6. Environment Variables & Configuration
| # | Source Variable | Purpose | Azure Equivalent | Auth Method | Notes |
|---|---------------|---------|------------------|-------------|-------|
| 1 | | | App Setting / Key Vault | Managed Identity / App Setting | |
## 7. Architecture Diagrams
### 7a. Current State (Source Platform)
<!-- Mermaid or ASCII diagram -->
### 7b. Target State (Azure)
<!-- Mermaid or ASCII diagram -->
## 8. Data Services Mapping
| Source Database/Storage | Azure Equivalent | Migration Path | Notes |
|------------------------|------------------|----------------|-------|
| | | | |
## 9. Networking & Security Mapping
| Source Feature | Azure Equivalent | Notes |
|---------------|------------------|-------|
| Custom domain | App Service Custom Domain | |
| SSL/TLS | App Service Managed Certificate / BYO cert | |
| VPN/VPC | VNet Integration | |
## 10. Monitoring & Observability Mapping
| Source Service | Azure Equivalent | Migration Notes |
|---------------|------------------|-----------------|
| | Application Insights | |
| | Azure Monitor Metrics | |
| | Azure Monitor Alerts | |
## 11. CI/CD & Deployment Mapping
| Source Tool | Azure Equivalent | Notes |
|------------|------------------|-------|
| | GitHub Actions / Azure DevOps | |
| | Bicep / ARM Templates | |
| | Deployment Slots | |
## 12. Recommendations
1. **Runtime**: <recommended App Service runtime stack and version>
2. **App Service Plan**: <Free / Basic / Standard / Premium v3>
3. **IaC Strategy**: <Bicep with azd / Terraform>
4. **Auth Strategy**: <Managed Identity for all service-to-service>
5. **Monitoring**: <Application Insights + Azure Monitor>
## 13. Next Steps
- [ ] Review and approve this assessment report
- [ ] Proceed to code migration (azure-cloud-migrate Phase 2)
- [ ] Hand off to azure-prepare for IaC generation
```
> 💡 **Tip:** Use `mcp_azure_mcp_get_azure_bestpractices` tool to learn App Service best practices for the comparison.
beanstalk-to-app-service.md 6.3 KB
# AWS Elastic Beanstalk to Azure App Service Migration
Detailed guidance for migrating AWS Elastic Beanstalk applications to Azure App Service.
## Service Mapping
| AWS Service | Azure Equivalent |
|-------------|------------------|
| Elastic Beanstalk | Azure App Service |
| Elastic Beanstalk Environment | App Service + App Service Plan |
| Platform Presets | Runtime Stacks |
| `.ebextensions/` | Bicep + App Settings |
| `Procfile` | Startup Command |
| RDS (PostgreSQL) | Azure Database for PostgreSQL Flexible Server |
| RDS (MySQL) | Azure Database for MySQL Flexible Server |
| RDS (SQL Server) | Azure SQL Database |
| ElastiCache (Redis) | Azure Cache for Redis |
| ElastiCache (Memcached) | Azure Cache for Redis |
| S3 | Azure Blob Storage |
| ALB / ELB | App Service built-in LB / Azure Front Door |
| Route 53 | Azure DNS |
| ACM (Certificate Manager) | App Service Managed Certificate |
| CloudWatch | Application Insights / Azure Monitor |
| CloudWatch Alarms | Azure Monitor Alerts |
| X-Ray | Application Insights (distributed tracing) |
| IAM Roles | Managed Identity + Azure RBAC |
| CodePipeline / CodeBuild | GitHub Actions / Azure DevOps |
| CloudFormation | Bicep / ARM Templates |
| Parameter Store | Azure App Configuration |
| Secrets Manager | Azure Key Vault |
| SQS | Azure Service Bus / Storage Queue |
| SNS | Azure Event Grid |
| Auto Scaling Group | App Service Autoscale |
| VPC | Azure VNet Integration |
| Security Groups | NSG + App Service Access Restrictions |
## Platform Preset → Runtime Stack Mapping
| Beanstalk Platform | App Service Runtime Stack |
|--------------------|---------------------------|
| Node.js 20 | Node 20 LTS |
| Node.js 18 | Node 18 LTS |
| Python 3.11 | Python 3.11 |
| Python 3.12 | Python 3.12 |
| Java 17 (Corretto) | Java 17 (Microsoft Build of OpenJDK) |
| Java 21 (Corretto) | Java 21 (Microsoft Build of OpenJDK) |
| .NET 8 on Linux | .NET 8 |
| Go (Docker) | Custom Container (Go) |
| Ruby | Custom Container (Ruby) |
| PHP 8.x | PHP 8.x |
| Docker | Azure App Service (Custom Container) |
## `.ebextensions/` Migration
Beanstalk `.ebextensions/` YAML configs map to Bicep and App Settings:
| `.ebextensions/` Feature | Azure Equivalent | Implementation |
|--------------------------|------------------|----------------|
| `option_settings` (env vars) | App Settings | Bicep `siteConfig.appSettings` |
| `packages` (yum/apt) | Startup script or Dockerfile | Custom container or startup command |
| `files` (config files) | Deployment package or Blob Storage | Include in app deployment |
| `commands` / `container_commands` | Startup command or deployment script | `az webapp config set --startup-file` |
| `Resources` (CloudFormation) | Bicep modules | Equivalent Azure resources |
| `services` (sysvinit) | WebJobs (continuous) or Azure Functions | Background processing — App Service "Always On" only prevents idle unload; it is NOT a replacement for sysvinit-style background services |
### Example: `.ebextensions/` → Bicep
```yaml
# .ebextensions/environment.config (AWS)
option_settings:
aws:elasticbeanstalk:application:environment:
NODE_ENV: production
DB_HOST: mydb.xxx.rds.amazonaws.com
```
```bicep
// Bicep equivalent
resource webApp 'Microsoft.Web/sites@2023-12-01' = {
properties: {
siteConfig: {
appSettings: [
{ name: 'NODE_ENV', value: 'production' }
{ name: 'PGHOST', value: postgresServer.properties.fullyQualifiedDomainName }
]
}
}
}
```
## RDS → Azure Database Migration
| RDS Feature | Azure Equivalent |
|-------------|------------------|
| Multi-AZ deployment | Zone-redundant HA |
| Read replicas | Read replicas |
| Automated backups | Automated backups (up to 35 days) |
| Parameter groups | Server parameters |
| Subnet groups | VNet integration + private endpoints |
| IAM authentication | Microsoft Entra authentication |
| RDS Proxy | Azure Database for PostgreSQL Flexible Server (built-in PgBouncer) |
> 💡 **Tip:** Use Azure Database Migration Service (DMS) for data migration from RDS.
## Auto Scaling Migration
| Beanstalk Scaling | App Service Autoscale |
|-------------------|----------------------|
| `MinSize` / `MaxSize` | Min/Max instance count |
| `Trigger` metric (CPU, Network) | Autoscale rules (CPU, Memory, HTTP queue) |
| `BreachDuration` | Time window + cool-down period |
| `ScalingAdjustment` | Scale-out/in increment |
| Time-based scaling | Schedule-based autoscale rules |
```bicep
resource autoscale 'Microsoft.Insights/autoscalesettings@2022-10-01' = {
properties: {
targetResourceUri: appServicePlan.id
profiles: [{
capacity: { minimum: '2', maximum: '10', default: '2' }
rules: [{
metricTrigger: {
metricName: 'CpuPercentage'
operator: 'GreaterThan'
threshold: 70
timeAggregation: 'Average'
timeWindow: 'PT5M'
}
scaleAction: {
direction: 'Increase'
type: 'ChangeCount'
value: '1'
cooldown: 'PT5M'
}
}]
}]
}
}
```
## ALB → App Service Load Balancing
| ALB Feature | Azure Equivalent |
|-------------|------------------|
| Path-based routing | App Service path mappings or Front Door rules |
| Host-based routing | Custom domains + Front Door |
| Health checks | App Service Health Check feature |
| SSL termination | Built-in TLS / Front Door |
| WAF | Azure Front Door WAF or App Gateway WAF |
| Sticky sessions | ARR Affinity (App Service) |
> For global load balancing with WAF, use **Azure Front Door** instead of App Service built-in LB.
## Environment Variables Migration
| Source | Target | Notes |
|--------|--------|-------|
| `option_settings` (env vars) | App Settings | Non-sensitive config |
| `option_settings` (secrets) | Key Vault references | `@Microsoft.KeyVault(SecretUri=...)` |
| Parameter Store refs | App Configuration | Shared config across environments |
| Secrets Manager refs | Key Vault | Secrets with rotation |
| `.env` file | App Settings + Key Vault | Split by sensitivity |
## Reference Links
- [AWS to Azure services comparison](https://learn.microsoft.com/en-us/azure/architecture/aws-professional/)
- [App Service overview](https://learn.microsoft.com/en-us/azure/app-service/overview)
- [App Service autoscale](https://learn.microsoft.com/en-us/azure/azure-monitor/autoscale/autoscale-get-started)
- [Azure Database Migration Service](https://learn.microsoft.com/en-us/azure/dms/dms-overview)
code-migration.md 4.9 KB
# Code Migration Phase
Migrate source platform web application code to Azure App Service.
## Prerequisites
- Assessment report completed
- Best practices loaded via `mcp_azure_mcp_get_azure_bestpractices` tool
## Rules
- Create all output in `<source-folder>-azure/` — never modify the source directory
- Use latest GA runtime stack for the target language
- Prefer managed identity over connection strings or API keys
- Use App Configuration for shared settings, Key Vault for secrets
- Always configure health check endpoint
## Steps
1. **Load Best Practices** — Use `mcp_azure_mcp_get_azure_bestpractices` tool for App Service guidance
2. **Create Project Structure** — Set up the project inside the output directory
3. **Migrate Application Code** — Adapt source code for App Service runtime
4. **Update Dependencies** — Replace platform-specific SDKs with Azure equivalents
5. **Configure Startup** — Set startup command or create Dockerfile
6. **Migrate Environment Variables** — Map to App Settings / App Configuration / Key Vault
7. **Configure Database Connections** — Switch to Azure database services with managed identity
8. **Add Health Check** — Implement `/healthz` endpoint for App Service health monitoring
9. **Set Up Logging** — Integrate Application Insights SDK
## Key Configuration Files
### Startup Command
For non-Docker deployments, configure the startup command in App Service:
| Runtime | Default Start | Custom Start |
|---------|--------------|--------------|
| Node.js | `npm start` | Set in Configuration → General Settings |
| Python | `gunicorn app:app` | `gunicorn --bind=0.0.0.0 --timeout 600 app:app` |
| Java | Auto-detected | `-Dserver.port=80` |
| .NET | Auto-detected | `dotnet myapp.dll` |
### Dockerfile (When Needed)
Use a Dockerfile when the app requires custom system dependencies or multi-process setups:
```dockerfile
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]
```
> ⚠️ **Port**: App Service injects `PORT` env var. Always bind to `process.env.PORT || 8080`.
### Application Insights Integration
```javascript
// Add as FIRST import in entry point
const appInsights = require('applicationinsights');
appInsights.setup(process.env.APPLICATIONINSIGHTS_CONNECTION_STRING)
.setAutoCollectRequests(true)
.setAutoCollectExceptions(true)
.start();
```
## Database Migration Patterns
| Source | Azure Target | Connection Pattern |
|--------|--------------|--------------------|
| RDS PostgreSQL | Azure Database for PostgreSQL Flexible Server | Managed identity + `@azure/identity` |
| RDS MySQL | Azure Database for MySQL Flexible Server | Managed identity + `@azure/identity` |
| RDS SQL Server | Azure SQL Database | Managed identity + `@azure/identity` |
| Heroku Postgres | Azure Database for PostgreSQL Flexible Server | Managed identity |
| Cloud SQL | Azure SQL / PostgreSQL Flexible Server | Managed identity |
| MongoDB Atlas | Azure Cosmos DB for MongoDB | Connection string → managed identity |
### Managed Identity Database Connection (Node.js)
```javascript
const { DefaultAzureCredential } = require('@azure/identity');
const { Client } = require('pg');
async function main() {
const credential = new DefaultAzureCredential({
managedIdentityClientId: process.env.AZURE_CLIENT_ID
});
const token = await credential.getToken('https://ossrdbms-aad.database.windows.net/.default');
const client = new Client({
host: process.env.PGHOST,
database: process.env.PGDATABASE,
user: process.env.PGUSER,
password: token.token,
ssl: { rejectUnauthorized: true },
port: 5432
});
await client.connect();
// ... use client
}
main().catch(console.error);
```
> ⚠️ Wrap in `async function main()` — top-level `await` is not supported in CommonJS or many Node.js entrypoints. For ESM, top-level await works only with `"type": "module"` in `package.json`.
## Static Assets & CDN
If the source app serves static assets, consider:
1. **Azure Blob Storage + CDN** for static files
2. **Azure Front Door** for global distribution
3. **App Service built-in static file serving** for simple cases
## Background Workers
| Source Pattern | Azure Equivalent |
|---------------|------------------|
| Heroku Worker dyno | WebJobs (continuous) or separate Container App |
| Beanstalk Worker tier | WebJobs or Azure Functions |
| App Engine service (worker) | WebJobs or Azure Functions |
| Cron jobs | WebJobs (triggered) or Azure Functions Timer trigger |
## Handoff to azure-prepare
After code migration is complete:
1. Update `migration-status.md` — mark Code Migration as ✅ Complete
2. Invoke **azure-prepare** — pass the assessment report context so it can:
- Use the service mapping as requirements input
- Generate IaC (Bicep/Terraform) for the mapped Azure services
- Create `azure.yaml` and `.azure/preparation-manifest.md`
- Apply security hardening
3. azure-prepare will then chain to **azure-validate** → **azure-deploy**
global-rules.md 2.8 KB
# Global Rules
These rules apply to ALL phases of App Service migration.
## Destructive Action Policy
⛔ **NEVER** perform destructive actions without explicit user confirmation via `ask_user`:
- Deleting files or directories
- Overwriting existing code
- Deploying to production environments
- Modifying existing Azure resources
- Removing source-platform resources
## User Confirmation Required
Always use `ask_user` before:
- Selecting Azure subscription
- Selecting Azure region/location
- Deploying infrastructure
- Making breaking changes to existing code
- Choosing App Service Plan tier (Free, Basic, Standard, Premium)
## Best Practices
- Always use `mcp_azure_mcp_get_azure_bestpractices` tool before generating Azure code
- Prefer managed identity over connection strings or API keys
- **Always use the latest supported runtime stack** — see the App Service [language overview](https://learn.microsoft.com/azure/app-service/overview-supported-languages) for the supported stacks page per language
- Follow Azure naming conventions
- Use Premium v3 or Standard plans for production workloads
- Enable health checks and diagnostic logging from day one
## Identity-First Authentication (Zero Secrets)
> Enterprise subscriptions commonly enforce policies that block local auth. Always design for identity-based access from the start.
- **Storage accounts**: Use identity-based connections with `DefaultAzureCredential`
- **Databases**: Use Microsoft Entra authentication for Azure SQL and PostgreSQL Flexible Server
- **Key Vault**: Use Key Vault references in App Settings (`@Microsoft.KeyVault(SecretUri=...)`)
- **Application Insights**: Configure ingestion via the connection string app setting (`APPLICATIONINSIGHTS_CONNECTION_STRING`). Use managed identity for management-plane access (querying, configuring components), not for telemetry ingestion
- **DefaultAzureCredential with UAMI**: Always pass `managedIdentityClientId` explicitly:
```javascript
const credential = new DefaultAzureCredential({
managedIdentityClientId: process.env.AZURE_CLIENT_ID
});
```
## App Service Specifics
- **Always enable HTTPS Only** — set `httpsOnly: true` in Bicep
- **Use 64-bit worker** for production — set `use32BitWorkerProcess: false`
- **Enable Always On** for Standard tier and above to prevent idle unload
- **Configure health check path** — `/healthz` or equivalent endpoint
- **Use deployment slots** for zero-downtime deployments in Standard tier+
- **Set minimum TLS to 1.2** — `minTlsVersion: '1.2'`
- **Enable managed identity** — prefer User Assigned for multi-resource scenarios
- **Use App Configuration** for shared settings across environments
- **Use Key Vault** for secrets — never store secrets in App Settings directly
## Output Directory
All migration output goes to `<source-folder>-azure/` at workspace root. Never modify the source directory.
heroku-to-app-service.md 7.5 KB
# Heroku to Azure App Service Migration
Detailed guidance for migrating Heroku applications to Azure App Service.
## Service Mapping
| Heroku Service | Azure Equivalent |
|---------------|------------------|
| Heroku App | Azure App Service |
| Dynos (Web) | App Service instances |
| Dynos (Worker) | WebJobs (continuous) or Azure Functions |
| Procfile | Startup command / Docker entrypoint |
| Buildpacks | Docker or App Service runtime stacks |
| Config Vars | App Settings / App Configuration / Key Vault |
| Heroku Postgres | Azure Database for PostgreSQL Flexible Server |
| Heroku Redis | Azure Cache for Redis |
| Heroku Kafka | Azure Event Hubs (Kafka-compatible) |
| Add-ons (general) | Azure managed services |
| Heroku Pipelines | GitHub Actions / Azure DevOps |
| Review Apps | Deployment Slots |
| Heroku CI | GitHub Actions |
| Heroku Scheduler | WebJobs (triggered) or Azure Functions Timer |
| Heroku ACM (SSL) | App Service Managed Certificate |
| Custom Domains | App Service Custom Domains |
| Heroku Logs / Logplex | Application Insights / Azure Monitor |
| Heroku Metrics | Azure Monitor Metrics |
| Heroku Connect | Azure Data Factory or Logic Apps |
| Private Spaces | App Service Environment (ASE) |
## Dyno → App Service Plan Mapping
| Heroku Dyno Type | App Service Plan | Notes |
|-------------------|------------------|-------|
| Free / Eco | Free (F1) | Dev/test only — F1 has shared compute with quota limits (CPU minutes), not equivalent to Heroku's 60 min/day cap. See [App Service Free SKU limits](https://learn.microsoft.com/azure/app-service/overview-hosting-plans#how-much-do-i-pay-for-the-free-and-shared-tiers) |
| Basic | Basic (B1) | No auto-scale, no slots |
| Standard-1X (512 MB) | Standard (S1) | Auto-scale, slots, custom domains |
| Standard-2X (1 GB) | Standard (S2) | More memory |
| Performance-M (2.5 GB) | Premium v3 (P1v3) | VNet, more instances |
| Performance-L (14 GB) | Premium v3 (P3v3) | High-memory workloads |
| Private-M / Private-L | App Service Environment (ASE) | Network-isolated |
## Procfile → Startup Configuration
### Web Process
| Procfile Entry | Azure Equivalent |
|---------------|------------------|
| `web: node server.js` | Startup command: `node server.js` |
| `web: gunicorn app:app` | Startup command: `gunicorn --bind=0.0.0.0 --timeout 600 app:app` |
| `web: java -jar target/app.jar` | Startup command: `java -jar app.jar` |
| `web: bundle exec puma -C config/puma.rb` | Custom container with Dockerfile |
### Worker Process
Heroku worker dynos have no direct App Service equivalent. Migration paths:
| Heroku Worker Pattern | Azure Equivalent |
|----------------------|------------------|
| `worker: node worker.js` | WebJob (continuous) |
| `worker: celery -A tasks worker` | Container App or AKS |
| `clock: node scheduler.js` | Azure Functions Timer trigger |
| `release: rake db:migrate` | Deployment slot swap scripts |
## Add-ons → Azure Managed Services
| Heroku Add-on | Azure Equivalent | Migration Notes |
|--------------|------------------|-----------------|
| Heroku Postgres | PostgreSQL Flexible Server | Use Azure DMS for data migration |
| Heroku Redis | Azure Cache for Redis | Export/import RDB or use replication |
| Heroku Kafka | Azure Event Hubs (Kafka API) | Compatible Kafka protocol |
| Papertrail | Application Insights | Structured logging migration |
| New Relic | Application Insights | APM feature parity |
| SendGrid | Azure Communication Services or SendGrid on Azure | SendGrid available via Azure Marketplace |
| Cloudinary | Azure Blob Storage + CDN | Media storage + delivery |
| Memcachier | Azure Cache for Redis | Redis supports memcache patterns |
| Bonsai (Elasticsearch) | Azure AI Search or Elastic on Azure | Search service migration |
| Heroku Scheduler | Azure Functions Timer trigger | Cron-based scheduling |
| Bucketeer (S3) | Azure Blob Storage | Object storage migration |
## Config Vars → App Configuration / Key Vault
| Config Var Type | Azure Target | Implementation |
|----------------|--------------|----------------|
| `DATABASE_URL` | App Setting (managed identity) | Use Entra auth, not connection string |
| `REDIS_URL` | App Setting | Azure Cache connection string or managed identity |
| `SECRET_KEY` | Key Vault reference | `@Microsoft.KeyVault(SecretUri=...)` |
| `API_KEY` (third-party) | Key Vault reference | Store in Key Vault |
| `NODE_ENV` / `RAILS_ENV` | App Setting | Add as a regular App Setting (e.g., `NODE_ENV=production`) — controls runtime behavior. Do NOT confuse with `WEBSITES_NODE_DEFAULT_VERSION`, which controls the Node.js engine version on Windows App Service. |
| `PORT` | Auto-injected | App Service sets `PORT` automatically |
| Feature flags | App Configuration | Feature management support built-in |
> ⚠️ **Warning:** Heroku's `DATABASE_URL` includes credentials. On Azure, use managed identity authentication instead. Never migrate connection strings with embedded passwords.
## Buildpacks → Docker
For apps using standard runtimes, use App Service runtime stacks directly. For custom buildpacks, migrate to Docker:
| Buildpack Scenario | Azure Approach |
|-------------------|----------------|
| Official Node.js buildpack | App Service Node.js runtime stack |
| Official Python buildpack | App Service Python runtime stack |
| Official Java buildpack | App Service Java runtime stack |
| Multi-buildpack (e.g., Node + Python) | Custom Dockerfile |
| Custom buildpack (apt packages) | Custom Dockerfile |
| Heroku CNB (Cloud Native Buildpack) | Azure Container Apps with CNB |
## Heroku Pipelines → GitHub Actions
| Pipeline Feature | Azure Equivalent |
|-----------------|------------------|
| Pipeline stages (dev → staging → prod) | GitHub Actions environments |
| Auto-deploy on push | GitHub Actions `on: push` trigger |
| Review Apps | Deployment slots with PR-based deploy |
| Pipeline promotion | Slot swap (staging → production) |
| Heroku CI test runner | GitHub Actions test job |
| Release phase | Pre-swap slot scripts |
### Example: GitHub Actions Deployment
```yaml
name: Deploy to App Service
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for azure/login@v2 OIDC token request
contents: read # Required to checkout the repo
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- uses: azure/webapps-deploy@v3
with:
app-name: '<app-name>'
slot-name: 'staging'
```
> ⚠️ Without the `permissions:` block, `azure/login@v2`'s OIDC token request fails with HTTP 403.
## Review Apps → Deployment Slots
| Heroku Review Apps | App Service Deployment Slots |
|-------------------|------------------------------|
| Auto-create per PR | Create via GitHub Actions per PR |
| Unique URL per PR | `<app>-<slot>.azurewebsites.net` |
| Auto-destroy on PR close | Delete slot in PR close workflow |
| Shared pipeline config | Slot-specific App Settings |
| `app.json` postdeploy scripts | Slot swap pre/post actions |
## Reference Links
- [Heroku to Azure migration guide](https://learn.microsoft.com/en-us/azure/app-service/overview)
- [App Service deployment slots](https://learn.microsoft.com/en-us/azure/app-service/deploy-staging-slots)
- [GitHub Actions for App Service](https://learn.microsoft.com/en-us/azure/app-service/deploy-github-actions)
- [App Service managed identity](https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity)
references/services/container-apps/
assessment-guide.md 7.0 KB
# Kubernetes to Azure Container Apps - Assessment Guide
## Compatibility Matrix
### Kubernetes → Container Apps Resource Mapping
| Kubernetes Concept | Container Apps Equivalent | Supported | Notes |
|-------------------|--------------------------|-----------|-------|
| Deployment | Container App | ✅ Yes | One-to-one mapping for stateless workloads |
| Service (ClusterIP) | Internal ingress | ✅ Yes | Set `ingress.external: false` |
| Service (LoadBalancer) | External ingress | ✅ Yes | Set `ingress.external: true` |
| Ingress | Built-in ingress with custom domain | ✅ Yes | Supports TLS, traffic splitting |
| ConfigMap | Environment variables | ✅ Yes | Inline or from secrets |
| Secret | Secrets (Key Vault refs preferred) | ✅ Yes | Use managed identity for Key Vault |
| CronJob | Container Apps Job (scheduled) | ✅ Yes | Cron expression syntax |
| Job | Container Apps Job (manual/event) | ✅ Yes | One-time or event-triggered |
| HPA | Built-in scaling rules | ✅ Yes | HTTP, TCP, KEDA-compatible scalers |
| PersistentVolumeClaim | Azure Files mount | ⚠️ Limited | EmptyDir and Azure Files only; no block storage |
| DaemonSet | N/A | ❌ No | Consider sidecar containers or external agents |
| StatefulSet | N/A | ❌ No | Use external state (Cosmos DB, Redis, SQL) |
| Custom CRDs / Operators | N/A | ❌ No | Evaluate if Dapr components can replace |
| NetworkPolicy | VNet NSG rules | ⚠️ Limited | Configure at Environment subnet level |
### Resource Limits
| Resource | Kubernetes (typical) | Container Apps Maximum | Migration Impact |
|----------|---------------------|----------------------|------------------|
| CPU per container | Up to 64+ vCPU | 4 vCPU | Split large containers |
| Memory per container | Up to 256+ GiB | 8 GiB | Redesign memory-intensive workloads |
| Replicas per app | 1000+ | 300 per revision | Validate scale requirements |
| Request timeout | Configurable (hours+) | 240 seconds default | Redesign long-running requests |
| Startup probe timeout | Configurable | 240 seconds | Optimize startup time |
| Containers per pod/app | 10+ | Up to 10 sidecars | Init + sidecar containers supported |
## Unsupported Patterns
### Critical Blockers
1. **StatefulSets with persistent storage**
- **Why**: Container Apps is designed for stateless workloads
- **Alternative**: Migrate state to Azure Cosmos DB, Azure SQL, Redis, or Storage
2. **DaemonSets for node-level agents**
- **Why**: No node-level access in managed environment
- **Alternative**: Use Azure Monitor agents, Dapr components, or sidecar containers
3. **Privileged containers or host networking**
- **Why**: Security isolation in managed platform
- **Alternative**: Redesign to avoid host-level access
4. **Custom CRDs and Operators**
- **Why**: No Kubernetes API server access
- **Alternative**: Use Dapr state management, bindings, or Azure PaaS services
5. **Direct Kubernetes API calls from apps**
- **Why**: Kubernetes API not exposed
- **Alternative**: Use environment variables, service discovery via DNS, or Dapr
### Storage Considerations
- **EmptyDir**: Supported (ephemeral storage)
- **Azure Files**: Supported via volume mounts
- **Persistent Block Storage**: Not supported (migrate to Azure Blob, SQL, Cosmos DB)
## Assessment Checklist
### 1. Workload Inventory
- List all Deployments, StatefulSets, DaemonSets in target namespaces
- Identify workload types: API, background worker, CronJob, StatefulSet
- Document current resource requests/limits (CPU, memory)
- Note replica counts (min, max, typical)
### 2. Network Configuration
- **Service Types**: ClusterIP (internal) vs LoadBalancer (external)
- **Ingress**: Document hostnames, TLS certificates, path routing rules
- **Service Mesh**: Document if using Istio, Linkerd (consider migrating to Dapr)
- **NetworkPolicies**: List egress/ingress rules (map to NSG rules or VNet integration)
### 3. Storage and State
- **PersistentVolumeClaims**: List volumes, sizes, access modes (ReadWriteOnce, ReadWriteMany)
- **StatefulSets**: Document state storage patterns (candidates for external state migration)
- **EmptyDir/Temp Storage**: Note usage patterns (supported in Container Apps)
- **ConfigMaps/Secrets**: Count and categorize (migrate inline or to Key Vault)
### 4. Scaling and Performance
- **HPA**: Document scaling metrics (CPU, memory, custom metrics)
- **Min/Max Replicas**: Verify within Container Apps limits (0-300)
- **Startup Time**: Measure pod startup latency (must be <240s)
- **Request Patterns**: Long-running requests (>240s) need redesign
### 5. Dependencies
- **Internal Services**: List service-to-service calls (use internal DNS in Container Apps)
- **External Services**: Databases, APIs, message queues, storage
- **Authentication**: Service accounts, RBAC roles (map to managed identities)
- **Observability**: Logging, metrics, tracing (migrate to Azure Monitor, App Insights)
### 6. CI/CD and Deployment
- **Pipeline Tools**: kubectl, Helm, Kustomize, ArgoCD, Flux
- **Image Registries**: Docker Hub, GCR, ECR, private registries (migrate to ACR)
- **Deployment Strategy**: Rolling update, blue/green, canary (Container Apps supports traffic splitting)
## Complexity Assessment Guidelines
### Low Complexity
- Stateless Deployments with ClusterIP or LoadBalancer Services
- Simple environment variables (no complex ConfigMaps)
- No persistent storage or external state already in use
- Standard HTTP/gRPC ingress
- No service mesh dependencies
### Medium Complexity
- Multiple Deployments with inter-service communication
- ConfigMaps and Secrets requiring Key Vault migration
- HPA with custom metrics (need KEDA scaler mapping)
- CronJobs (map to Container Apps Jobs)
- Ingress with TLS and custom domains
### High Complexity
- StatefulSets requiring state migration to external services
- Service mesh (Istio/Linkerd) requiring Dapr migration
- Custom CRDs or Operators (need redesign)
- NetworkPolicies requiring VNet/NSG configuration
- Large-scale deployments (>100 replicas, need architecture review)
- Workloads exceeding Container Apps resource limits (>4 vCPU, >8 GiB)
## Assessment Report Structure
Generate `k8s-migration-assessment.md` with:
1. **Executive Summary**: Cluster name, namespace(s), workload count, complexity (Low/Medium/High), estimated timeline, Azure cost
2. **Current State**: Deployment inventory, resource usage, scaling config, storage usage, networking topology
3. **Compatibility Analysis**: Supported workloads, blockers, redesign requirements (StatefulSets, DaemonSets, CRDs)
4. **Azure Target**: Required resources (resource group, Container Apps Environment, ACR, Key Vault, Log Analytics, VNet if needed)
5. **Migration Plan**:
- State migration strategy (databases, caches, storage)
- Image migration approach (ACR import, rebuild)
- IaC generation plan (Bicep templates per Deployment)
- Deployment sequence (dependencies first, then consumers)
6. **Risk Assessment**: Blockers, feature gaps, performance considerations, downtime estimate
7. **Validation Tests**: Smoke tests, integration tests, performance benchmarks
cloudrun-assessment-guide.md 2.5 KB
# Assessment: Cloud Run to Container Apps
## Checklist
### 1. Service Configuration
- **CPU/Memory**: Cloud Run (1–4 vCPU, 128 MiB–32 GiB) → Container Apps (0.25–4 vCPU, 0.5–8 Gi)
- **Images**: Registry location (GCR or Artifact Registry), image size, base image
- **Port**: Exposed port (Cloud Run default 8080)
- **Environment Variables**: Static values, secret references, service URLs
### 2. Request Handling
- **Concurrency**: Per instance (default 80, max 1000) → Container Apps (1–300)
- **Min/Max Instances**: 0–1000 → Container Apps 0–300 per revision
- **Timeout**: Max 60 min → Container Apps max 30 min (1800s)
- **CPU Allocation**: Request-based vs always → Container Apps always allocated
- **HTTP/2, WebSockets, gRPC**: Document if used
### 3. Networking
- **Ingress**: Public, internal (VPC), or internal + load balancing
- **Custom Domains**: List domains and SSL certificates
- **VPC Connector**: Region, IP range, connected VPC
- **Dependencies**: Cloud SQL, Firestore, Cloud Storage, Pub/Sub, Redis, external APIs
### 4. IAM & Security
- **Service Account**: Default or custom
- **IAM Roles**: Storage, Firestore, Pub/Sub, Secret Manager, Cloud SQL permissions
- Task role policies → Managed Identity + Azure RBAC
- Secret Manager access → Key Vault RBAC (recommended) or access policies for vaults still using access-policy mode
### 5. Observability
- **Logging**: Destinations, structured logs (JSON)
- **Monitoring**: Request metrics, CPU/memory, instance count
- **Tracing**: Cloud Trace → Application Insights
### 6. Event-Driven
- **Eventarc**: Pub/Sub triggers, Cloud Storage triggers
- **Cloud Scheduler**: Schedule (cron), target endpoint
### 7. Cost Analysis
- Cloud Run: Request charges, CPU/memory time
- Data transfer egress charges
- Container Registry storage
## Resource Mapping
| Cloud Run Config | Container Apps Equivalent |
|------------------|--------------------------|
| `--concurrency 80` | `--scale-rule-http-concurrency 80` |
| `--min-instances 0` | `--min-replicas 0` |
| `--max-instances 10` | `--max-replicas 10` |
| `--cpu 1` | `--cpu 1.0` |
| `--memory 512Mi` | `--memory 1Gi` |
| `--port 8080` | `--target-port 8080` |
| `--timeout 300` | ingress timeout 300s |
## Complexity Rating
- **Low**: Single container, public ingress, standard env vars, no VPC
- **Medium**: Internal ingress, Pub/Sub triggers, custom service account, Cloud Scheduler
- **High**: Complex traffic management, VPC networking, multiple Eventarc triggers, long-running requests (>30 min)
cloudrun-deployment-guide.md 7.9 KB
# Deployment: Cloud Run to Container Apps
## Prerequisites
Azure CLI 2.53+, gcloud CLI, Docker, ACR, Key Vault, Log Analytics
## Phase 1: Image Migration
### Bash
```bash
set -euo pipefail
GCP_PROJECT="${GCP_PROJECT:-<project>}"
GCP_REGION="${GCP_REGION:-<region>}"
ACR_NAME="${ACR_NAME:-<acr>}"
gcloud auth configure-docker "${GCP_REGION}-docker.pkg.dev"
az acr login --name "$ACR_NAME"
for img in "app:v1" "worker:v1"; do
docker pull "${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT}/<repo>/$img"
docker tag "${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT}/<repo>/$img" "${ACR_NAME}.azurecr.io/$img"
docker push "${ACR_NAME}.azurecr.io/$img"
done
```
### PowerShell
```powershell
$GCP_PROJECT = if ($env:GCP_PROJECT) { $env:GCP_PROJECT } else { "<project>" }
$GCP_REGION = if ($env:GCP_REGION) { $env:GCP_REGION } else { "<region>" }
$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" }
gcloud auth configure-docker "${GCP_REGION}-docker.pkg.dev"
az acr login --name $ACR_NAME
@("app:v1", "worker:v1") | ForEach-Object {
docker pull "${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT}/<repo>/$_"
docker tag "${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT}/<repo>/$_" "${ACR_NAME}.azurecr.io/$_"
docker push "${ACR_NAME}.azurecr.io/$_"
}
```
## Phase 2: Infrastructure
> Choose ONE path: basic (without VNet) OR VNet-integrated.
### Basic (no VNet)
#### Bash
```bash
set -euo pipefail
az group create --name "$RG" --location "$LOCATION"
az monitor log-analytics workspace create -g "$RG" -n "${RG}-logs" -l "$LOCATION"
LOG_ID=$(az monitor log-analytics workspace show -g "$RG" -n "${RG}-logs" --query customerId -o tsv)
LOG_KEY=$(az monitor log-analytics workspace get-shared-keys -g "$RG" -n "${RG}-logs" --query primarySharedKey -o tsv)
az containerapp env create -n "${RG}-env" -g "$RG" -l "$LOCATION" \
--logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY"
```
#### PowerShell
```powershell
az group create --name $RG --location $LOCATION
az monitor log-analytics workspace create -g $RG -n "${RG}-logs" -l $LOCATION
$workspace = az monitor log-analytics workspace show -g $RG -n "${RG}-logs" | ConvertFrom-Json
$keys = az monitor log-analytics workspace get-shared-keys -g $RG -n "${RG}-logs" | ConvertFrom-Json
az containerapp env create -n "${RG}-env" -g $RG -l $LOCATION `
--logs-workspace-id $workspace.customerId --logs-workspace-key $keys.primarySharedKey
```
### VNet-Integrated
#### Bash
```bash
set -euo pipefail
az network vnet create -g "$RG" -n "${RG}-vnet" \
--address-prefix 10.0.0.0/16 --subnet-name aca-subnet --subnet-prefix 10.0.0.0/23
SUBNET_ID=$(az network vnet subnet show -g "$RG" --vnet-name "${RG}-vnet" -n aca-subnet --query id -o tsv)
az containerapp env create -n "${RG}-env" -g "$RG" -l "$LOCATION" \
--logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY" \
--infrastructure-subnet-resource-id "$SUBNET_ID"
```
#### PowerShell
```powershell
az network vnet create -g $RG -n "${RG}-vnet" `
--address-prefix 10.0.0.0/16 --subnet-name aca-subnet --subnet-prefix 10.0.0.0/23
$subnet = az network vnet subnet show -g $RG --vnet-name "${RG}-vnet" -n aca-subnet | ConvertFrom-Json
az containerapp env create -n "${RG}-env" -g $RG -l $LOCATION `
--logs-workspace-id $workspace.customerId --logs-workspace-key $keys.primarySharedKey `
--infrastructure-subnet-resource-id $subnet.id
```
## Phase 3: Secrets & Identity
### Bash
```bash
set -euo pipefail
az keyvault create --name "$KEY_VAULT" -g "$RG" -l "$LOCATION"
IDENTITY_ID=$(az identity create -n "${RG}-id" -g "$RG" -l "$LOCATION" --query id -o tsv)
PRINCIPAL_ID=$(az identity show --ids "$IDENTITY_ID" --query principalId -o tsv)
# Grant Key Vault access — use RBAC (recommended) or access policies
# Option A: RBAC (default for new vaults)
KV_ID=$(az keyvault show --name "$KEY_VAULT" --query id -o tsv)
az role assignment create --assignee "$PRINCIPAL_ID" \
--role "Key Vault Secrets User" --scope "$KV_ID"
# Option B: Access policies (if vault uses access-policy mode)
# az keyvault set-policy --name "$KEY_VAULT" --object-id "$PRINCIPAL_ID" --secret-permissions get list
# Migrate secrets without writing them to disk
az keyvault secret set --vault-name "$KEY_VAULT" --name <secret-name> \
--value "$(gcloud secrets versions access latest --secret=<secret-id> --project="$GCP_PROJECT")"
# ACR pull access
ACR_ID=$(az acr show --name "$ACR_NAME" --query id -o tsv)
az role assignment create --assignee "$PRINCIPAL_ID" --role AcrPull --scope "$ACR_ID"
```
### PowerShell
```powershell
az keyvault create --name $KEY_VAULT -g $RG -l $LOCATION
$identity = az identity create -n "${RG}-id" -g $RG -l $LOCATION | ConvertFrom-Json
$principalId = (az identity show --ids $identity.id | ConvertFrom-Json).principalId
# Grant Key Vault access — RBAC (recommended)
$kvId = (az keyvault show --name $KEY_VAULT | ConvertFrom-Json).id
az role assignment create --assignee $principalId `
--role "Key Vault Secrets User" --scope $kvId
# Migrate secrets without writing them to disk
$secretValue = gcloud secrets versions access latest --secret=<secret-id> --project=$GCP_PROJECT
az keyvault secret set --vault-name $KEY_VAULT --name <secret-name> --value $secretValue
Remove-Variable secretValue
# ACR pull access
$acrId = (az acr show --name $ACR_NAME | ConvertFrom-Json).id
az role assignment create --assignee $principalId --role AcrPull --scope $acrId
```
## Phase 4: Deploy Container App
### Bash
```bash
set -euo pipefail
SECRET_URI=$(az keyvault secret show --vault-name "$KEY_VAULT" --name db-pw --query id -o tsv)
az containerapp create \
--name <app-name> -g "$RG" --environment "${RG}-env" \
--image "${ACR_NAME}.azurecr.io/app:v1" --target-port 8080 --ingress external \
--cpu 1.0 --memory 1Gi --min-replicas 0 --max-replicas 10 \
--user-assigned "$IDENTITY_ID" --registry-identity "$IDENTITY_ID" \
--registry-server "${ACR_NAME}.azurecr.io" \
--secrets db-pw=keyvaultref:"${SECRET_URI}",identityref:"${IDENTITY_ID}" \
--env-vars ENV=prod DB_PASSWORD=secretref:db-pw \
--scale-rule-name http --scale-rule-type http --scale-rule-http-concurrency 80
```
### PowerShell
```powershell
$secret = az keyvault secret show --vault-name $KEY_VAULT --name db-pw | ConvertFrom-Json
az containerapp create `
--name <app-name> -g $RG --environment "${RG}-env" `
--image "${ACR_NAME}.azurecr.io/app:v1" --target-port 8080 --ingress external `
--cpu 1.0 --memory 1Gi --min-replicas 0 --max-replicas 10 `
--user-assigned $identity.id --registry-identity $identity.id `
--registry-server "${ACR_NAME}.azurecr.io" `
--secrets "db-pw=keyvaultref:$($secret.id),identityref:$($identity.id)" `
--env-vars "ENV=prod" "DB_PASSWORD=secretref:db-pw" `
--scale-rule-name http --scale-rule-type http --scale-rule-http-concurrency 80
```
### Configuration Mapping
| Cloud Run | Container Apps |
|-----------|----------------|
| `--min-instances 0` | `--min-replicas 0` |
| `--max-instances 10` | `--max-replicas 10` |
| `--concurrency 80` | `--scale-rule-http-concurrency 80` |
| `--cpu 1` | `--cpu 1.0` |
| `--memory 512Mi` | `--memory 1Gi` |
## Phase 5: Validation
### Bash
```bash
FQDN=$(az containerapp show --name <app-name> -g "$RG" --query properties.configuration.ingress.fqdn -o tsv)
curl -I "https://$FQDN/health"
az containerapp logs show --name <app-name> -g "$RG" --tail 100
```
### PowerShell
```powershell
$app = az containerapp show --name <app-name> -g $RG | ConvertFrom-Json
Invoke-WebRequest -Uri "https://$($app.properties.configuration.ingress.fqdn)/health"
az containerapp logs show --name <app-name> -g $RG --tail 100
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Image pull fails | Verify ACR role: `az role assignment list --assignee $PRINCIPAL_ID --scope $ACR_ID -o table` |
| App won't start | Check logs: `az containerapp logs show --name <app> -g $RG --tail 100` |
| Secret not accessible | Verify RBAC: `az role assignment list --assignee $PRINCIPAL_ID --scope $KV_ID -o table` |
| Scaling not working | Check config: `az containerapp show --name <app> --query properties.template.scale` |
cloudrun-to-container-apps.md 2.2 KB
# Google Cloud Run → Azure Container Apps
Detailed guidance for migrating Cloud Run serverless containers to Azure Container Apps.
## Overview
| Cloud Run | Azure Container Apps |
|-----------|---------------------|
| Cloud Run Service | Container App |
| Artifact Registry / GCR | Azure Container Registry (ACR) |
| Secret Manager | Azure Key Vault |
| Cloud Logging | Azure Monitor / Log Analytics |
| VPC Connector | VNet Integration |
## Critical Differences
| Feature | Cloud Run | Container Apps | Impact |
|---------|-----------|----------------|--------|
| Max Timeout | 60 min | 30 min (1800s) | Redesign long-running tasks |
| CPU Allocation | Request-based or always | Always allocated | Cost model changes |
| Max Instances | 0–1000 | 0–300 per revision | Validate instance needs |
| Concurrency | 1–1000 | 1–300 | Adjust concurrency settings |
| Startup Time | 10 min max | 240s default | Validate startup time |
## Migration Workflow
1. **Assess** — Analyze Cloud Run config → [cloudrun-assessment-guide.md](cloudrun-assessment-guide.md)
2. **Images** — Migrate GCR/Artifact Registry → ACR
3. **Config** — Convert YAML, secrets → Key Vault, set up infrastructure
4. **Deploy** — Deploy to Container Apps → [cloudrun-deployment-guide.md](cloudrun-deployment-guide.md)
5. **Validate** — Health checks, logs, scaling verification
## Service Dependency Mappings
| GCP Service | Azure Equivalent | Notes |
|-------------|------------------|-------|
| Cloud SQL (PostgreSQL) | Azure Database for PostgreSQL | Connection string + managed identity |
| Cloud SQL (MySQL) | Azure Database for MySQL | Connection string + managed identity |
| Firestore | Azure Cosmos DB | SDK change required |
| Cloud Storage | Azure Blob Storage | SDK change required |
| Cloud Memorystore (Redis) | Azure Cache for Redis | Connection string update |
| Pub/Sub | Azure Service Bus / Event Grid | SDK change required |
| Cloud Tasks | Azure Queue Storage / Service Bus | SDK change required |
| Secret Manager | Azure Key Vault | Managed identity integration |
| Cloud Logging | Azure Monitor Logs | Auto-configured |
| Cloud Scheduler | Azure Logic Apps / Functions Timer | HTTP trigger to Container Apps |
deployment-guide.md 9.6 KB
# Kubernetes to Azure Container Apps - Deployment Guide
## Prerequisites
Azure CLI 2.53.0+, kubectl, Docker, Azure subscription, ACR, Key Vault, Log Analytics
## Phase 1: Export Kubernetes Resources
```bash
kubectl get deployments,services,ingress -n <namespace> -o wide
kubectl get configmaps,secrets -n <namespace>
```
### Export Script
```bash
#!/bin/bash
set -euo pipefail
NAMESPACE="${K8S_NAMESPACE:-<namespace>}"
OUTPUT_DIR="${OUTPUT_DIR:-k8s-export}"
mkdir -p "$OUTPUT_DIR"
kubectl get deploy,svc,ingress,configmap,secret -n "$NAMESPACE" -o yaml > "$OUTPUT_DIR/all-resources.yaml"
for deploy in $(kubectl get deploy -n "$NAMESPACE" -o jsonpath='{.items[*].metadata.name}'); do
kubectl get deployment "$deploy" -n "$NAMESPACE" -o yaml > "$OUTPUT_DIR/deploy-${deploy}.yaml"
done
```
## Phase 2: Assess Compatibility
Load [assessment-guide.md](assessment-guide.md). Check: StatefulSets, DaemonSets, CRDs, resource limits (>4 vCPU/>8 GiB), PVCs, NetworkPolicies.
## Phase 3: Migrate Images
> ⚠️ **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}}'`.
### Bash
```bash
#!/bin/bash
set -euo pipefail
ACR_NAME="${ACR_NAME:-<acr>}"
SOURCE_REGISTRY="${SOURCE_REGISTRY:-<registry>}"
az acr login --name "$ACR_NAME"
az acr import --name "$ACR_NAME" --source "${SOURCE_REGISTRY}/app:v1.0" --image app:v1.0
```
### PowerShell
```powershell
$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" }
$SOURCE_REGISTRY = if ($env:SOURCE_REGISTRY) { $env:SOURCE_REGISTRY } else { "<registry>" }
az acr login --name $ACR_NAME
az acr import --name $ACR_NAME --source "${SOURCE_REGISTRY}/app:v1.0" --image app:v1.0
```
## Phase 4: Infrastructure
### Bash
```bash
az group create --name myapp-rg --location eastus
az monitor log-analytics workspace create --resource-group myapp-rg --workspace-name myapp-logs --location eastus
LOG_ID=$(az monitor log-analytics workspace show --resource-group myapp-rg --workspace-name myapp-logs --query customerId -o tsv)
LOG_KEY=$(az monitor log-analytics workspace get-shared-keys --resource-group myapp-rg --workspace-name myapp-logs --query primarySharedKey -o tsv)
az containerapp env create --name myapp-env --resource-group myapp-rg --location eastus --logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY"
```
### PowerShell
```powershell
az group create --name myapp-rg --location eastus
az monitor log-analytics workspace create --resource-group myapp-rg --workspace-name myapp-logs --location eastus
$LOG_ID = az monitor log-analytics workspace show --resource-group myapp-rg --workspace-name myapp-logs --query customerId -o tsv
$LOG_KEY = az monitor log-analytics workspace get-shared-keys --resource-group myapp-rg --workspace-name myapp-logs --query primarySharedKey -o tsv
az containerapp env create --name myapp-env --resource-group myapp-rg --location eastus --logs-workspace-id $LOG_ID --logs-workspace-key $LOG_KEY
```
**VNet:** For VNet integration, create VNet first, get subnet ID, then use `--infrastructure-subnet-resource-id` with env create.
## Phase 5: Secrets
> **Tip**: Prefer piping decoded secret values directly to `az keyvault secret set --value` to avoid writing sensitive data to disk.
### Bash
```bash
ACR_NAME="${ACR_NAME:-<acr>}"
# Create Key Vault and migrate secrets (pipe directly — no temp file)
az keyvault create --name myapp-kv --resource-group myapp-rg --location eastus
az keyvault secret set --vault-name myapp-kv --name password --value "$(kubectl get secret mysecret -n <namespace> -o jsonpath='{.data.password}' | base64 -d)"
# Create managed identity and grant permissions
az identity create --name myapp-id --resource-group myapp-rg --location eastus
IDENTITY_ID=$(az identity show --name myapp-id --resource-group myapp-rg --query id -o tsv)
PRINCIPAL_ID=$(az identity show --name myapp-id --resource-group myapp-rg --query principalId -o tsv)
KV_ID=$(az keyvault show --name myapp-kv --resource-group myapp-rg --query id -o tsv)
az role assignment create --assignee "$PRINCIPAL_ID" --role "Key Vault Secrets User" --scope "$KV_ID"
ACR_ID=$(az acr show --name "$ACR_NAME" --query id -o tsv)
az role assignment create --assignee "$PRINCIPAL_ID" --role AcrPull --scope "$ACR_ID"
```
### PowerShell
```powershell
$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" }
# Create Key Vault and migrate secrets (pass directly — no temp file, no BOM issues)
az keyvault create --name myapp-kv --resource-group myapp-rg --location eastus
$secretValue = kubectl get secret mysecret -n <namespace> -o jsonpath='{.data.password}'
$decodedSecretValue = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($secretValue))
az keyvault secret set --vault-name myapp-kv --name password --value $decodedSecretValue
Remove-Variable -Name decodedSecretValue, secretValue -ErrorAction SilentlyContinue
# Create managed identity and grant permissions
az identity create --name myapp-id --resource-group myapp-rg --location eastus
$IDENTITY_ID = az identity show --name myapp-id --resource-group myapp-rg --query id -o tsv
$PRINCIPAL_ID = az identity show --name myapp-id --resource-group myapp-rg --query principalId -o tsv
$KV_ID = az keyvault show --name myapp-kv --resource-group myapp-rg --query id -o tsv
az role assignment create --assignee $PRINCIPAL_ID --role "Key Vault Secrets User" --scope $KV_ID
$ACR_ID = az acr show --name $ACR_NAME --query id -o tsv
az role assignment create --assignee $PRINCIPAL_ID --role AcrPull --scope $ACR_ID
```
## Phase 6: Deploy
> ⚠️ **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.
**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`.
```bash
# Get Key Vault secret URI and deploy
SECRET_URI=$(az keyvault secret show --vault-name myapp-kv --name password --query id -o tsv)
az containerapp create --name my-app --resource-group myapp-rg --environment myapp-env \
--image $ACR_NAME.azurecr.io/app:v1.0 --target-port 8080 --ingress external \
--cpu 1.0 --memory 2Gi --min-replicas 2 --max-replicas 10 \
--user-assigned $IDENTITY_ID --registry-identity $IDENTITY_ID --registry-server $ACR_NAME.azurecr.io \
--secrets password=keyvaultref:$SECRET_URI,identityref:$IDENTITY_ID \
--env-vars ENV=prod DB_PASSWORD=secretref:password \
--scale-rule-name http --scale-rule-type http --scale-rule-http-concurrency 80
```
```powershell
$SECRET_URI = az keyvault secret show --vault-name myapp-kv --name password --query id -o tsv
az containerapp create --name my-app --resource-group myapp-rg --environment myapp-env `
--image "$ACR_NAME.azurecr.io/app:v1.0" --target-port 8080 --ingress external `
--cpu 1.0 --memory 2Gi --min-replicas 2 --max-replicas 10 `
--user-assigned $IDENTITY_ID --registry-identity $IDENTITY_ID --registry-server "$ACR_NAME.azurecr.io" `
--secrets "password=keyvaultref:$SECRET_URI,identityref:$IDENTITY_ID" `
--env-vars ENV=prod DB_PASSWORD=secretref:password `
--scale-rule-name http --scale-rule-type http --scale-rule-http-concurrency 80
```
## Phase 7: Validation
```bash
# Get app FQDN and test
FQDN=$(az containerapp show --name my-app --resource-group myapp-rg --query properties.configuration.ingress.fqdn -o tsv)
echo "App URL: https://$FQDN"
curl https://$FQDN/health
# View logs
az containerapp logs show --name my-app --resource-group myapp-rg --follow
```
```powershell
$FQDN = az containerapp show --name my-app --resource-group myapp-rg --query properties.configuration.ingress.fqdn -o tsv
Write-Host "App URL: https://$FQDN"
Invoke-WebRequest -Uri "https://$FQDN/health" -Method Head
# View logs
az containerapp logs show --name my-app --resource-group myapp-rg --follow
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Image pull | Verify ACR: `az acr check-health --name $ACR_NAME`; check AcrPull role |
| Wrong architecture | ACA requires linux/amd64. Check: `docker inspect <image> --format '{{.Architecture}}'`. Rebuild with `--platform linux/amd64` |
| Port mismatch | Verify `targetPort` matches app port |
| OOM | Increase memory limit (up to 4 vCPU / 8 GiB max per container) |
| DNS | Retrieve FQDN: `az containerapp show --name <app> -g <rg> --query properties.configuration.ingress.fqdn -o tsv` |
| 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 |
| SecretRef not found | `--env-vars KEY=secretref:name` requires `--secrets name=value` (or keyvaultref) in the **same** `az containerapp create` command |
| 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 |
| 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 |
fargate-assessment-guide.md 2.4 KB
# Assessment: Fargate to Container Apps
## Checklist
### 1. Container Configuration
- **CPU/Memory**: Extract from task definition and verify the requested CPU/memory fits within Azure Container Apps limits for the target environment/region
- **Container Images**: Registry location (ECR), image size, base image
- **Port Mappings**: Exposed ports and protocols
### 2. Environment & Secrets
- Static configuration values (env vars)
- Secret references: Secrets Manager ARNs → Key Vault URLs with managed identity
- Parameter Store references → App Configuration or Key Vault
- Service discovery endpoints
### 3. Networking
- VPC subnets (public vs private) → VNet integration
- Security groups → NSG rules
- Load balancer type (ALB/NLB) → Container Apps ingress (built-in HTTPS)
- Health check configuration and SSL/TLS certificates
### 4. IAM & Security
- Task role policies → Managed Identity + Azure RBAC
- ECR pull permissions → ACR role assignment (AcrPull)
- Secrets Manager access → Key Vault RBAC (recommended) or access policies for vaults still using access-policy mode
### 5. Dependencies
- **Databases**: RDS → Azure Database for PostgreSQL/MySQL/SQL
- **Cache**: ElastiCache → Azure Cache for Redis
- **Storage**: S3 → Azure Blob Storage (SDK: boto3 → azure-storage-blob)
- **Messaging**: SQS/SNS → Service Bus / Event Grid
- **Monitoring**: CloudWatch → Azure Monitor / Log Analytics (requires Log Analytics workspace on Container Apps environment)
### 6. Scaling & Performance
- Auto scaling policies (target tracking, min/max tasks)
- Request rate and latency requirements
- Actual CPU/memory usage vs allocation
## Resource Mapping
| ECS Task Definition | Container Apps Equivalent |
|---------------------|--------------------------|
| `cpu: "512"` (0.5 vCPU) | `cpu: 0.5` |
| `memory: "1024"` (MB) | `memory: 1Gi` |
| `containerPort` | `ingress.targetPort` |
| `environment` | `env` array |
| `secrets` (Secrets Manager ARN) | `secrets` with `keyVaultUrl` + `identity` |
| `logConfiguration` (awslogs) | Log Analytics (requires workspace on environment) |
| Service Auto Scaling | `scale.rules` (HTTP/CPU/memory/custom) |
## Complexity Rating
- **Low**: Single container, no VPC, standard env vars, public ingress
- **Medium**: Multiple containers, VPC with private connectivity, secrets, custom IAM
- **High**: Multi-service dependencies, VPN/Direct Connect, cross-account IAM, stateful workloads
fargate-deployment-guide.md 10.1 KB
# Deployment: Fargate to Container Apps
## Prerequisites
Azure CLI 2.53+ with `containerapp` extension, AWS CLI v2, Docker, ACR, Key Vault, Log Analytics
## Phase 1: Container Registry Migration
```bash
set -euo pipefail
aws ecr get-login-password --region "$AWS_REGION" | \
docker login --username AWS --password-stdin "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
az acr login --name "$ACR_NAME"
docker pull "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${IMAGE}"
docker tag "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${IMAGE}" "${ACR_NAME}.azurecr.io/${IMAGE}"
docker push "${ACR_NAME}.azurecr.io/${IMAGE}"
```
```powershell
$ErrorActionPreference = 'Stop'
$ecrPassword = aws ecr get-login-password --region $env:AWS_REGION
$ecrPassword | docker login --username AWS --password-stdin "$($env:AWS_ACCOUNT_ID).dkr.ecr.$($env:AWS_REGION).amazonaws.com"
az acr login --name $env:ACR_NAME
docker pull "$($env:AWS_ACCOUNT_ID).dkr.ecr.$($env:AWS_REGION).amazonaws.com/$($env:IMAGE)"
docker tag "$($env:AWS_ACCOUNT_ID).dkr.ecr.$($env:AWS_REGION).amazonaws.com/$($env:IMAGE)" "$($env:ACR_NAME).azurecr.io/$($env:IMAGE)"
docker push "$($env:ACR_NAME).azurecr.io/$($env:IMAGE)"
```
## Phase 2: Infrastructure
> Choose ONE path: basic (without VNet) OR VNet-integrated.
### Basic (no VNet)
```bash
set -euo pipefail
az group create --name "$RG" --location "$LOCATION"
az monitor log-analytics workspace create -g "$RG" -n "${RG}-logs" -l "$LOCATION"
LOG_ID=$(az monitor log-analytics workspace show -g "$RG" -n "${RG}-logs" --query customerId -o tsv)
# Keyless (recommended): avoids handling the shared key entirely
az containerapp env create -n "${RG}-env" -g "$RG" -l "$LOCATION" \
--logs-destination azure-monitor --logs-workspace-id "$LOG_ID"
# Fallback: use shared key if azure-monitor destination is not available
# LOG_KEY=$(az monitor log-analytics workspace get-shared-keys -g "$RG" -n "${RG}-logs" --query primarySharedKey -o tsv)
# az containerapp env create -n "${RG}-env" -g "$RG" -l "$LOCATION" \
# --logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY"
```
```powershell
$ErrorActionPreference = 'Stop'
az group create --name $env:RG --location $env:LOCATION
az monitor log-analytics workspace create -g $env:RG -n "$($env:RG)-logs" -l $env:LOCATION
$logId = az monitor log-analytics workspace show -g $env:RG -n "$($env:RG)-logs" --query customerId -o tsv
# Keyless (recommended): avoids handling the shared key entirely
az containerapp env create -n "$($env:RG)-env" -g $env:RG -l $env:LOCATION `
--logs-destination azure-monitor --logs-workspace-id $logId
# Fallback: use shared key if azure-monitor destination is not available
# $logKey = az monitor log-analytics workspace get-shared-keys -g $env:RG -n "$($env:RG)-logs" --query primarySharedKey -o tsv
# az containerapp env create -n "$($env:RG)-env" -g $env:RG -l $env:LOCATION `
# --logs-workspace-id $logId --logs-workspace-key $logKey
```
### VNet-Integrated
```bash
set -euo pipefail
az group create --name "$RG" --location "$LOCATION"
az monitor log-analytics workspace create -g "$RG" -n "${RG}-logs" -l "$LOCATION"
LOG_ID=$(az monitor log-analytics workspace show -g "$RG" -n "${RG}-logs" --query customerId -o tsv)
az network vnet create -g "$RG" -n "${RG}-vnet" \
--address-prefix 10.0.0.0/16 --subnet-name aca-subnet --subnet-prefix 10.0.0.0/23
SUBNET_ID=$(az network vnet subnet show -g "$RG" --vnet-name "${RG}-vnet" -n aca-subnet --query id -o tsv)
az containerapp env create -n "${RG}-env" -g "$RG" -l "$LOCATION" \
--logs-destination azure-monitor --logs-workspace-id "$LOG_ID" \
--infrastructure-subnet-resource-id "$SUBNET_ID"
```
```powershell
$ErrorActionPreference = 'Stop'
az group create --name $env:RG --location $env:LOCATION
az monitor log-analytics workspace create -g $env:RG -n "$($env:RG)-logs" -l $env:LOCATION
$logId = az monitor log-analytics workspace show -g $env:RG -n "$($env:RG)-logs" --query customerId -o tsv
az network vnet create -g $env:RG -n "$($env:RG)-vnet" `
--address-prefix 10.0.0.0/16 --subnet-name aca-subnet --subnet-prefix 10.0.0.0/23
$subnet = az network vnet subnet show -g $env:RG --vnet-name "$($env:RG)-vnet" -n aca-subnet | ConvertFrom-Json
az containerapp env create -n "$($env:RG)-env" -g $env:RG -l $env:LOCATION `
--logs-destination azure-monitor --logs-workspace-id $logId `
--infrastructure-subnet-resource-id $subnet.id
```
## Phase 3: Secrets & Identity
```bash
set -euo pipefail
az keyvault create --name "$KEY_VAULT" -g "$RG" -l "$LOCATION" \
--enable-rbac-authorization true
IDENTITY_ID=$(az identity create -n "${RG}-id" -g "$RG" -l "$LOCATION" --query id -o tsv)
PRINCIPAL_ID=$(az identity show --ids "$IDENTITY_ID" --query principalId -o tsv)
# Grant Key Vault access — use RBAC (recommended) or access policies
# Option A: RBAC (enabled on the vault created above)
KV_ID=$(az keyvault show --name "$KEY_VAULT" --query id -o tsv)
az role assignment create --assignee "$PRINCIPAL_ID" \
--role "Key Vault Secrets User" --scope "$KV_ID"
# Option B: Access policies (if vault uses access policy mode)
# az keyvault set-policy --name "$KEY_VAULT" --object-id "$PRINCIPAL_ID" --secret-permissions get list
# Migrate secrets more safely: avoid passing the secret as a CLI argument.
# Use a locked-down temporary file, import with --file, and remove it immediately.
# Do not run this in shared, monitored, or recorded environments.
umask 077
secret_file="$(mktemp)"
trap 'rm -f "$secret_file"' EXIT
aws secretsmanager get-secret-value --secret-id <secret-id> --region <region> \
--query SecretString --output text > "$secret_file"
az keyvault secret set --vault-name "$KEY_VAULT" --name <secret-name> \
--file "$secret_file"
rm -f "$secret_file"
trap - EXIT
# ACR pull access
ACR_ID=$(az acr show --name "$ACR_NAME" --query id -o tsv)
az role assignment create --assignee "$PRINCIPAL_ID" --role AcrPull --scope "$ACR_ID"
```
```powershell
$ErrorActionPreference = 'Stop'
az keyvault create --name $env:KEY_VAULT -g $env:RG -l $env:LOCATION --enable-rbac-authorization true
$identityId = az identity create -n "$($env:RG)-id" -g $env:RG -l $env:LOCATION --query id -o tsv
$principalId = az identity show --ids $identityId --query principalId -o tsv
# Grant Key Vault access — use RBAC (recommended) or access policies
# Option A: RBAC (enabled on the vault created above)
$kvId = az keyvault show --name $env:KEY_VAULT --query id -o tsv
az role assignment create --assignee $principalId `
--role "Key Vault Secrets User" --scope $kvId
# Option B: Access policies (if vault uses access policy mode)
# az keyvault set-policy --name $env:KEY_VAULT --object-id $principalId --secret-permissions get list
# Migrate secrets more safely: use a temp file instead of passing as CLI argument.
# Do not run this in shared, monitored, or recorded environments.
$secretFile = [System.IO.Path]::GetTempFileName()
try {
aws secretsmanager get-secret-value --secret-id <secret-id> --region <region> `
--query SecretString --output text | Set-Content -Path $secretFile -NoNewline
az keyvault secret set --vault-name $env:KEY_VAULT --name <secret-name> `
--file $secretFile
} finally {
Remove-Item -Path $secretFile -Force -ErrorAction SilentlyContinue
}
# ACR pull access
$acrId = az acr show --name $env:ACR_NAME --query id -o tsv
az role assignment create --assignee $principalId --role AcrPull --scope $acrId
```
## Phase 4: Deploy
```bash
set -euo pipefail
SECRET_URI=$(az keyvault secret show --vault-name "$KEY_VAULT" --name db-password --query id -o tsv)
az containerapp create --name <app-name> -g "$RG" --environment "${RG}-env" \
--image "${ACR_NAME}.azurecr.io/<image>:<tag>" --target-port 8080 --ingress external \
--cpu 0.5 --memory 1Gi --min-replicas 1 --max-replicas 10 \
--user-assigned "$IDENTITY_ID" --registry-identity "$IDENTITY_ID" \
--registry-server "${ACR_NAME}.azurecr.io" \
--secrets db-pass=keyvaultref:"${SECRET_URI}",identityref:"${IDENTITY_ID}" \
--env-vars ENV=production DB_PASSWORD=secretref:db-pass
```
```powershell
$ErrorActionPreference = 'Stop'
$secretUri = az keyvault secret show --vault-name $env:KEY_VAULT --name db-password --query id -o tsv
az containerapp create --name <app-name> -g $env:RG --environment "$($env:RG)-env" `
--image "$($env:ACR_NAME).azurecr.io/<image>:<tag>" --target-port 8080 --ingress external `
--cpu 0.5 --memory 1Gi --min-replicas 1 --max-replicas 10 `
--user-assigned $identityId --registry-identity $identityId `
--registry-server "$($env:ACR_NAME).azurecr.io" `
--secrets "db-pass=keyvaultref:$($secretUri),identityref:$($identityId)" `
--env-vars ENV=production DB_PASSWORD=secretref:db-pass
```
### Configuration Mapping
| ECS Task Definition | Container Apps CLI |
|---------------------|--------------------|
| `cpu: "512"` (0.5 vCPU) | `--cpu 0.5` |
| `memory: "1024"` (1 GB) | `--memory 1Gi` |
| `containerPort: 8080` | `--target-port 8080` |
| `desiredCount: 2` | `--min-replicas 2` |
| `secrets` (Secrets Manager ARN) | `--secrets name=keyvaultref:URI,identityref:ID` |
| `environment` (env vars) | `--env-vars KEY=value` |
## Phase 5: Validate
```bash
set -euo pipefail
FQDN=$(az containerapp show --name <app-name> -g "$RG" --query properties.configuration.ingress.fqdn -o tsv)
curl -f -I "https://$FQDN/health" || { echo "Health check failed"; exit 1; }
az containerapp logs show --name <app-name> -g "$RG" --tail 100
```
```powershell
$ErrorActionPreference = 'Stop'
$fqdn = az containerapp show --name <app-name> -g $env:RG --query properties.configuration.ingress.fqdn -o tsv
Invoke-WebRequest -Uri "https://$fqdn/health" -Method Head
az containerapp logs show --name <app-name> -g $env:RG --tail 100
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Image pull fails | Verify ACR role: `az role assignment list --assignee $(az identity show --ids <identity-resource-id> --query principalId -o tsv) --scope $(az acr show -n <acr-name> --query id -o tsv)` |
| App won't start | Check logs: `az containerapp logs show --name <app-name> -g <resource-group> --tail 100` |
| Secret not accessible | Verify RBAC: `az role assignment list --assignee $(az identity show --ids <identity-resource-id> --query principalId -o tsv) --scope $(az keyvault show -n <vault-name> --query id -o tsv)` |
fargate-to-container-apps.md 3.9 KB
# AWS Fargate to Azure Container Apps Migration
Guidance for migrating AWS Fargate (ECS) containerized workloads to Azure Container Apps.
## Overview
| AWS Service | Azure Equivalent | Notes |
|-------------|------------------|-------|
| ECS Fargate | Azure Container Apps | Serverless container platform |
| ECR | Azure Container Registry | Private container registry |
| Application Load Balancer | Container Apps Ingress | Built-in HTTPS |
| AWS Secrets Manager | Azure Key Vault | Managed identity integration |
| CloudWatch Logs | Azure Monitor/Log Analytics | Requires Log Analytics workspace on environment |
| CloudWatch Metrics | Azure Monitor Metrics | Available without Log Analytics workspace |
| IAM Roles (tasks) | Managed Identity | Microsoft Entra ID integration |
| VPC | Virtual Network | VNet integration |
| Security Groups | NSG + Container Apps rules | Network security |
| Auto Scaling | Container Apps scaling rules | HTTP, CPU, memory, custom |
| Parameter Store | App Configuration or Key Vault | Configuration management |
## Critical Differences
| Feature | AWS Fargate | Container Apps | Impact |
|---------|-------------|----------------|--------|
| Max vCPU | 16 vCPU | 4 vCPU (Consumption Plan) | Split CPU-intensive tasks |
| Max Memory | 120 GiB | 8 GiB (Consumption Plan) | Redesign memory-heavy workloads |
| GPU | Available (ECS) | Supported (preview) | Validate GPU SKU availability |
| Sidecar Containers | Supported (ECS) | Supported (init + sidecar) | Compatible pattern |
| Task Placement | Placement strategies/constraints | No node-level control | Remove placement logic |
| Persistent Storage | EFS mounts | Azure Files only | Migrate to Azure Files or Blob |
| Max Replicas | Service-dependent | 300 per revision | Validate scale requirements |
| Networking | VPC + Security Groups | VNet + NSG | Subnet-level isolation |
| Startup Timeout | No platform limit | 240s default | Optimize startup time |
## Migration Workflow
1. **Assess** — Analyze ECS task definitions, IAM roles, VPC config → [fargate-assessment-guide.md](fargate-assessment-guide.md)
2. **Migrate Images** — Pull from ECR, push to ACR
3. **Map Services** — Convert AWS dependencies to Azure equivalents
4. **Convert Config** — Transform task definitions to Container Apps CLI flags
5. **Deploy** — Create Container Apps environment and deploy → [fargate-deployment-guide.md](fargate-deployment-guide.md)
6. **Validate** — Health checks, scaling, monitoring
## Service Dependency Mappings
| AWS Service | Azure Equivalent | Notes |
|-------------|------------------|-------|
| S3 | Azure Blob Storage | SDK change (boto3 → azure-storage-blob) |
| DynamoDB | Azure Cosmos DB | API compatibility or code changes |
| SQS | Azure Service Bus / Queue Storage | SDK change |
| SNS | Azure Event Grid / Service Bus Topics | SDK change |
| RDS | Azure Database for PostgreSQL/MySQL/SQL | Connection string + managed identity |
| ElastiCache (Redis) | Azure Cache for Redis | Connection string update |
| Parameter Store | Azure App Configuration / Key Vault | SDK change |
| EventBridge | Azure Event Grid | SDK change required |
| Step Functions | Azure Logic Apps / Durable Functions | Workflow redesign |
| Kinesis | Azure Event Hubs | SDK change required |
| X-Ray | Application Insights | Requires SDK + connection string setup |
| CloudWatch Logs | Azure Monitor Logs | Requires Log Analytics workspace on environment |
## Resource Mapping
See [Resource Mapping](fargate-assessment-guide.md#resource-mapping) in the assessment guide for the canonical ECS-to-Container-Apps resource mapping table.
## Reference Links
- [Azure Container Apps overview](https://learn.microsoft.com/azure/container-apps/overview)
- [AWS to Azure services comparison](https://learn.microsoft.com/azure/architecture/aws-professional/services)
- [Container Apps scaling](https://learn.microsoft.com/azure/container-apps/scale-app)
- [Container Apps managed identity](https://learn.microsoft.com/azure/container-apps/managed-identity)
k8s-to-container-apps.md 4.9 KB
# Kubernetes to Azure Container Apps Migration
Detailed guidance for migrating containerized workloads from Kubernetes (GKE, EKS, self-hosted) to Azure Container Apps.
## Overview
| Kubernetes Source | Azure Equivalent |
|-------------------|------------------|
| GKE / EKS / Self-hosted Kubernetes | Azure Container Apps |
| Docker Registry / GCR / ECR | Azure Container Registry (ACR) |
| ConfigMap | Container Apps Environment Variables / Secrets |
| Secret | Azure Key Vault + Key Vault references |
| Ingress | Container Apps Ingress |
| Service (LoadBalancer/ClusterIP) | Container Apps Ingress (external/internal) |
| HPA (Horizontal Pod Autoscaler) | Container Apps Scaling Rules |
| Namespace | Container Apps Environment |
| Persistent Volume | Azure Files / Blob Storage (via volume mounts) |
## Resource Mapping
| Kubernetes Resource | Container Apps Equivalent | Notes |
|--------------|---------------------------|-------|
| Deployment | Container App | One deployment → one Container App |
| Service (type: LoadBalancer) | Ingress (external: true) | Public endpoint |
| Service (type: ClusterIP) | Ingress (external: false) | Internal only |
| ConfigMap | `env` with plaintext values | Use Key Vault for sensitive data |
| Secret | `secretRef` + Key Vault | Managed identity for access |
| HPA | `scale` rules (http, cpu, memory, custom) | HTTP concurrency, queue depth, etc. |
| Ingress | Ingress configuration | Automatic HTTPS, custom domains |
| Liveness/Readiness Probe | Health probes | HTTP, TCP, or startup probes |
## Configuration Mapping
| Kubernetes Manifest | Container Apps CLI/Bicep | Example |
|--------------|--------------------------|---------|
| `replicas: 3` | `--min-replicas 3 --max-replicas 3` | Static scaling |
| `resources.requests.cpu` | `--cpu 0.5` | CPU cores (0.25-4.0) |
| `resources.requests.memory` | `--memory 1Gi` | Memory (0.5Gi-8Gi) |
| `image: gcr.io/my-registry/app:v1` | `--image myacr.azurecr.io/app:v1` | After ACR import |
| `env: - name: KEY, value: val` | `--env-vars KEY=val` | Environment variables |
| `env: - name: SECRET, valueFrom: secretKeyRef` | `--secrets SECRET=keyvaultref:...` | Key Vault reference |
| `ports: - containerPort: 8080` | `--target-port 8080` | Container port |
| `livenessProbe.httpGet.path: /health` | YAML/Bicep `probes` config | Health probes not configurable via CLI |
## Migration Workflow
Follow these phases sequentially:
### Phase 1: Export Kubernetes Resources
- Use `kubectl get deployment,service,configmap,secret -o yaml` to export manifests
- Document current configuration (replicas, resources, env vars)
- Identify external dependencies (databases, message queues, storage)
### Phase 2: Assess Compatibility
- Verify workloads are stateless (Container Apps doesn't support StatefulSets)
- Check for unsupported features (DaemonSets, custom CRDs, Operators)
- Plan Jobs/CronJobs migration to Container Apps Jobs
- Identify ConfigMaps/Secrets requiring Key Vault migration
- Review persistent storage needs (migrate to Azure Files/Blob)
See [assessment-guide.md](assessment-guide.md) for detailed checklist.
### Phase 3: Migrate Container Images
- Create Azure Container Registry: `az acr create`
- Import images from GCR/ECR/Docker Hub: `az acr import`
- Or rebuild and push: `docker build` → `docker push`
- Enable managed identity access: `az containerapp registry set`
### Phase 4: Deploy to Container Apps
- Create Container Apps Environment
- Deploy Container Apps with converted configuration
- Configure ingress, scaling rules, and health probes
- Set up Key Vault references for secrets
See [deployment-guide.md](deployment-guide.md) for step-by-step deployment.
### Phase 5: Verify and Test
- Test external endpoints (HTTP/HTTPS ingress)
- Test internal service-to-service communication
- Verify environment variables and secrets
- Validate scaling behavior
- Check health probes and logs
## Unsupported Features
Container Apps **does NOT support**:
- StatefulSets (use Azure Database services instead)
- DaemonSets (not applicable in serverless model)
- Kubernetes PV/PVC objects (use Azure Files/Blob Storage via Container Apps volume mounts instead)
- Custom CNI networking
- Node affinity / pod affinity
For batch and scheduled workloads, migrate Kubernetes **Jobs / CronJobs** to **Azure Container Apps Jobs** instead of long-running Container Apps.
For unsupported Kubernetes platform features, consider **Azure Kubernetes Service (AKS)** instead.
## Best Practices
1. **Use Managed Identity** for ACR and Key Vault access (no passwords)
2. **Store secrets in Key Vault**, reference them in Container Apps
3. **Use Container Apps Environments** to group related microservices
4. **Enable Dapr** for service-to-service communication, state management, pub/sub
5. **Configure health probes** to ensure reliability
6. **Use scaling rules** based on HTTP concurrency or custom metrics
7. **Never modify source Kubernetes cluster** during migration
spring-apps-to-aca.md 3.7 KB
# Spring Boot to Azure Container Apps Migration
> Migrate Spring Boot applications from Azure Spring Apps, VMs, or other platforms to Azure Container Apps with containerization, deployment, and Spring Cloud integration.
## Overview
This migration enables you to move existing Spring Boot applications from Azure Spring Apps (or any platform) to Azure Container Apps with minimal code changes.
## Migration Scenarios
| Source Platform | Target Platform | Migration Path |
|----------------|-----------------|----------------|
| Azure Spring Apps | Azure Container Apps | Assess → Containerize → Deploy → Spring Cloud components |
| Spring Boot on VMs | Azure Container Apps | Assess → Containerize → Deploy |
| Spring Boot (other cloud) | Azure Container Apps | Assess → Containerize → Deploy |
## Service Mapping
| Spring Apps Feature | Container Apps Equivalent |
|-------------------|---------------------------|
| App Deployment | Container App |
| Service Registry (Eureka) | Managed Eureka for Spring (`eureka-server-for-spring`); alt: Dapr service invocation + internal DNS |
| Config Server | Managed Config Server for Spring (`config-server-for-spring`); alt: Azure App Configuration + Key Vault |
| Spring Cloud Gateway | Managed Gateway for Spring; alt: Azure API Management / Container Apps ingress |
| Distributed Tracing | Application Insights |
| Log Streaming | Log Analytics Workspace |
## Pre-Migration Assessment
Assess your Spring Boot application for migration readiness:
- **Local State**: Check for in-memory sessions, singletons, file-based state → [spring-assessment-guide.md](spring-assessment-guide.md#local-state-assessment)
- **File System**: Identify file writes, temp files, shared storage needs → [spring-assessment-guide.md](spring-assessment-guide.md#file-system-usage)
- **Platform Compatibility**: Verify Java 17+, Spring Boot 3.x support → [spring-assessment-guide.md](spring-assessment-guide.md#platform-compatibility)
- **External Resources**: Inventory databases, message brokers, caches → [spring-assessment-guide.md](spring-assessment-guide.md#external-resources-inventory)
## Migration Workflow
1. **Assess** — Analyze application for migration readiness
> Present findings and complexity rating to user. Ask: "Assessment complete — proceed with containerization?"
2. **Containerize** — Create Dockerfile, build image, push to ACR
3. **Provision** — Create Container Apps environment, configure logging
4. **Deploy** — Deploy container to Azure Container Apps
5. **Optimize** — Add Spring Cloud components (Config, Eureka, Gateway)
See [spring-deployment-guide.md](spring-deployment-guide.md) for detailed phase-by-phase instructions.
## Key Differences from Azure Spring Apps
| Aspect | Azure Spring Apps | Container Apps |
|--------|-------------------|----------------|
| Deployment Unit | JAR/WAR | Container Image |
| Service Discovery | Built-in Eureka | Managed Eureka component (or Dapr / DNS) |
| Configuration Management | Built-in Config Server | Managed Config Server component (or App Configuration) |
| Scaling | Auto-scaling | HTTP/CPU/Memory/Custom metrics |
| Networking | VNet injection | VNet integration + Internal ingress |
## Best Practices
- Use managed identity for Azure resource access (no connection strings)
- Store secrets in Azure Key Vault, reference via environment variables
- Use Application Insights for observability
- Enable Log Analytics for centralized logging
- Use Azure Files for persistent storage if needed
## Next Steps After Migration
After successful migration:
1. Configure Spring Cloud components (optional)
2. Set up CI/CD pipeline with GitHub Actions / Azure DevOps
3. Configure custom domains and SSL certificates
4. Implement autoscaling rules
5. Hand off to `azure-prepare` for infrastructure optimization
spring-assessment-guide.md 5.2 KB
# Pre-Migration Assessment for Spring Boot to Azure Container Apps
## Local State Assessment
| Issue | Impact | Solution |
|-------|--------|----------|
| Singleton patterns | Multiple instances may run during updates | Refactor to stateless design |
| In-memory sessions | Lost during restarts/scaling | Migrate to Azure Cache for Redis |
| Local caching | Not shared across replicas | Use Azure Cache for Redis with Spring Data Redis |
| File-based state | Lost on restart | Migrate to Azure Cosmos DB, Azure SQL, or Azure Storage |
**State Migration Options:**
- **Azure Cache for Redis**: Session data, distributed caching
- **Azure Cosmos DB**: NoSQL data, document storage
- **Azure SQL/MySQL/PostgreSQL**: Relational data
- **Azure Storage Blobs**: Unstructured data, serialized objects
## File System Usage
| Pattern | Container Apps Solution |
|---------|------------------------|
| Temporary files | Ephemeral storage (automatic, deleted on restart) |
| Shared persistent data | Azure Files storage mounts |
| Static content serving | Azure Blob Storage + Azure CDN |
| User uploads | Azure Blob Storage with Azure Function triggers |
## Platform Compatibility
### Supported Java Versions
- **For Spring Boot 2.x source apps**: Java 8 or 11 supported for assessment
- **For Spring Boot 3.x target apps**: Java 17 or 21 required (verify with `java -version`)
### Spring Boot Version Requirements
- **Recommended target**: Spring Boot 3.x (requires Java 17+)
- **Supported source**: Spring Boot 2.x on Java 8/11 → plan upgrade to Java 17+ and follow [Spring Boot 3.0 Migration Guide](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0-Migration-Guide)
### Spring Cloud Compatibility
- Verify Spring Cloud version matches Spring Boot 3.x requirements
- See [Spring Cloud versions](https://spring.io/projects/spring-cloud#overview)
## External Resources Inventory
Inventory all external dependencies. See [spring-dependency-patterns.md](spring-dependency-patterns.md) for configuration examples (databases, message brokers, caches).
### Identity Providers
| Provider | Configuration |
|----------|---------------|
| OAuth2 | Spring Security reference docs |
| Auth0 | Auth0 Spring Security documentation |
| PingFederate | Spring Security SAML 2.0/OIDC docs, PingIdentity docs |
| Microsoft Entra ID | Update redirect URIs to new Container Apps FQDN |
## Scheduled Jobs Assessment
| Job Type | Container Apps Solution |
|----------|------------------------|
| Unix cron jobs | Azure Container Apps Jobs (ephemeral) |
| Spring Batch tasks | Azure Container Apps Jobs |
| Quartz scheduler | Long-running app (handle scale-out race conditions) |
| Scheduled @Scheduled methods | Long-running app (handle multiple instances) |
## Configuration & Secrets
### Port Configuration
- Default: 8080
- Change via `server.port` or `SERVER_PORT` environment variable
- Configure in Container Apps ingress settings
### Secrets Checklist
1. Inventory all secrets in application.properties/application.yml
2. Document database passwords, API keys, connection strings
3. Plan migration to Azure Key Vault
4. Update app to use Key Vault references or environment variables
### Certificates
- Run `keytool -list -v -keystore <path>` to document SSL certificates
- Plan migration to Azure Key Vault or Container Apps managed certificates
## Deployment Architecture
**Document current state:**
- Number of instances
- CPU per instance (vCPU)
- RAM per instance (GiB)
- Regional distribution
- Uptime requirements/SLA
**Container Apps Limits:**
- Max 4 vCPU per container
- Max 8 GiB RAM per container
- Max 300 replicas per revision
## Assessment Checklist
- [ ] Identified all local state and planned migration to external storage
- [ ] Reviewed file system usage and selected storage solution
- [ ] Verified Java version compatibility (17+ for Spring Boot 3.x target; 8/11 for source assessment only)
- [ ] Confirmed Spring Boot 3.x or planned upgrade
- [ ] Inventoried databases (MySQL, PostgreSQL, MongoDB, Cosmos DB)
- [ ] Inventoried message brokers (ActiveMQ, IBM MQ, Azure Service Bus)
- [ ] Inventoried external caches (Redis)
- [ ] Documented identity providers (OAuth2, SAML, Entra ID)
- [ ] Identified scheduled jobs and selected execution model
- [ ] Listed all configuration secrets for Key Vault migration
- [ ] Documented SSL certificates
- [ ] Recorded current deployment architecture (instances, CPU, RAM)
- [ ] Reviewed logging configuration (console vs. file)
- [ ] Identified APM tools (Application Insights, custom agents)
## Complexity Guidelines
**Low Complexity:**
- Stateless Spring Boot app with external database
- No scheduled jobs or simple @Scheduled tasks
- Standard Java version (11, 17, 21)
- OAuth2/SAML authentication
**Medium Complexity:**
- In-memory session state requiring Redis migration
- File system writes requiring Azure Files or Blob Storage
- Spring Boot 2.x requiring upgrade to 3.x
- Message broker integration (ActiveMQ, Service Bus)
- Scheduled jobs requiring Jobs configuration
**High Complexity:**
- Multiple stateful components (local cache, sessions, file storage)
- Custom JVM agents or APM integrations
- Java 8 requiring upgrade to 11+
- Complex scheduled jobs with coordination requirements
- Multi-region deployment with traffic distribution
- Custom identity provider federation
spring-dependency-patterns.md 1.2 KB
# Spring Dependency Configuration Patterns
Common dependency and configuration patterns to identify during assessment.
## Database Configuration
**Maven (pom.xml):**
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
```
**application.properties:**
```properties
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=dbuser
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
```
**application.yml:**
```yaml
spring:
data:
mongodb:
uri: mongodb://<username>:<password>@server:27017
```
## JMS Message Brokers
**ActiveMQ (pom.xml):**
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
```
**application.properties:**
```properties
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.user=admin
```
## External Caches
**Redis with Spring Data Redis:**
- Check for `spring-boot-starter-data-redis` dependency
- Review application.properties for Redis connection strings
- Check for Spring Session configuration (in-memory → Redis)
spring-deployment-guide.md 15.1 KB
# Deployment Guide: Spring Boot to Azure Container Apps
## Phase 1: Create Container Apps Environment
**Bash:**
```bash
#!/bin/bash
set -euo pipefail
az group create --name spring-rg --location eastus
az monitor log-analytics workspace create --resource-group spring-rg --workspace-name spring-logs --location eastus
LOG_ID=$(az monitor log-analytics workspace show --resource-group spring-rg --workspace-name spring-logs --query customerId -o tsv)
LOG_KEY=$(az monitor log-analytics workspace get-shared-keys --resource-group spring-rg --workspace-name spring-logs --query primarySharedKey -o tsv)
az containerapp env create --name spring-env --resource-group spring-rg --location eastus --logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY"
```
**PowerShell:**
```powershell
az group create --name spring-rg --location eastus
az monitor log-analytics workspace create --resource-group spring-rg --workspace-name spring-logs --location eastus
$LOG_ID = az monitor log-analytics workspace show --resource-group spring-rg --workspace-name spring-logs --query customerId -o tsv
$LOG_KEY = az monitor log-analytics workspace get-shared-keys --resource-group spring-rg --workspace-name spring-logs --query primarySharedKey -o tsv
az containerapp env create --name spring-env --resource-group spring-rg --location eastus --logs-workspace-id "$LOG_ID" --logs-workspace-key "$LOG_KEY"
```
## Phase 2: Configure Logging
**Update application.properties:**
```properties
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
```
Configure diagnostic settings: Azure Monitor Log Analytics (recommended), Event Hubs, or third-party solutions.
## Phase 3: Containerize Application
**Dockerfile:**
```dockerfile
FROM mcr.microsoft.com/openjdk/jdk:21-ubuntu
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
```
**Build and push (Bash):**
```bash
ACR_NAME="${ACR_NAME:-<acr>}"
az acr create --name "$ACR_NAME" --resource-group spring-rg --sku Basic --location eastus
az acr login --name "$ACR_NAME"
docker build -t "${ACR_NAME}.azurecr.io/spring-app:v1.0" .
docker push "${ACR_NAME}.azurecr.io/spring-app:v1.0"
```
**Build and push (PowerShell):**
```powershell
$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" }
az acr create --name "$ACR_NAME" --resource-group spring-rg --sku Basic --location eastus
az acr login --name "$ACR_NAME"
docker build -t "${ACR_NAME}.azurecr.io/spring-app:v1.0" .
docker push "${ACR_NAME}.azurecr.io/spring-app:v1.0"
```
## Phase 4: Configure Storage (if needed)
**Azure Files for persistent storage (Bash):**
```bash
STORAGE_ACCOUNT="${STORAGE_ACCOUNT:-<storage-account>}"
az storage account create --name "$STORAGE_ACCOUNT" --resource-group spring-rg --location eastus --sku Standard_LRS
STORAGE_KEY=$(az storage account keys list --account-name "$STORAGE_ACCOUNT" --resource-group spring-rg --query "[0].value" -o tsv)
az storage share create --name spring-data --account-name "$STORAGE_ACCOUNT" --account-key "$STORAGE_KEY"
az containerapp env storage set --name spring-env --resource-group spring-rg --storage-name spring-storage \
--azure-file-account-name "$STORAGE_ACCOUNT" --azure-file-account-key "$STORAGE_KEY" \
--azure-file-share-name spring-data --access-mode ReadWrite
```
**Azure Files for persistent storage (PowerShell):**
```powershell
$STORAGE_ACCOUNT = if ($env:STORAGE_ACCOUNT) { $env:STORAGE_ACCOUNT } else { "<storage-account>" }
az storage account create --name "$STORAGE_ACCOUNT" --resource-group spring-rg --location eastus --sku Standard_LRS
$STORAGE_KEY = az storage account keys list --account-name "$STORAGE_ACCOUNT" --resource-group spring-rg --query "[0].value" -o tsv
az storage share create --name spring-data --account-name "$STORAGE_ACCOUNT" --account-key "$STORAGE_KEY"
az containerapp env storage set --name spring-env --resource-group spring-rg --storage-name spring-storage `
--azure-file-account-name "$STORAGE_ACCOUNT" --azure-file-account-key "$STORAGE_KEY" `
--azure-file-share-name spring-data --access-mode ReadWrite
```
## Phase 5: Migrate Secrets to Key Vault
> **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.
**Bash:**
```bash
ACR_NAME="${ACR_NAME:-<acr>}" # From Phase 3
KEY_VAULT="${KEY_VAULT:-<keyvault>}"
az keyvault create --name "$KEY_VAULT" --resource-group spring-rg --location eastus
IDENTITY_ID=$(az identity create --name spring-id --resource-group spring-rg --location eastus --query id -o tsv)
PRINCIPAL_ID=$(az identity show --ids "$IDENTITY_ID" --query principalId -o tsv)
az keyvault set-policy --name "$KEY_VAULT" --object-id "$PRINCIPAL_ID" --secret-permissions get list
# Secure approach using temp file
SECRET_FILE=$(mktemp)
trap 'shred -u "$SECRET_FILE" 2>/dev/null || rm -f "$SECRET_FILE"' EXIT
read -s -p "Enter database password: " DB_PASSWORD
echo -n "$DB_PASSWORD" > "$SECRET_FILE"
az keyvault secret set --vault-name "$KEY_VAULT" --name db-password --file "$SECRET_FILE"
ACR_ID=$(az acr show --name "$ACR_NAME" --query id -o tsv)
az role assignment create --assignee "$PRINCIPAL_ID" --role AcrPull --scope "$ACR_ID"
```
**PowerShell:**
```powershell
$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" } # From Phase 3
$KEY_VAULT = if ($env:KEY_VAULT) { $env:KEY_VAULT } else { "<keyvault>" }
az keyvault create --name "$KEY_VAULT" --resource-group spring-rg --location eastus
$IDENTITY_ID = az identity create --name spring-id --resource-group spring-rg --location eastus --query id -o tsv
$PRINCIPAL_ID = az identity show --ids "$IDENTITY_ID" --query principalId -o tsv
az keyvault set-policy --name "$KEY_VAULT" --object-id "$PRINCIPAL_ID" --secret-permissions get list
# Secure approach using temp file
$SECRET_FILE = [System.IO.Path]::GetTempFileName()
try {
$SecurePassword = Read-Host "Enter database password" -AsSecureString
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword)
try {
$PlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
[System.IO.File]::WriteAllText($SECRET_FILE, $PlainPassword)
az keyvault secret set --vault-name "$KEY_VAULT" --name db-password --file "$SECRET_FILE"
} finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
}
} finally {
Remove-Item $SECRET_FILE -Force -ErrorAction SilentlyContinue
}
$ACR_ID = az acr show --name "$ACR_NAME" --query id -o tsv
az role assignment create --assignee "$PRINCIPAL_ID" --role AcrPull --scope "$ACR_ID"
```
## Phase 6: Deploy Container App
**Bash:**
```bash
ACR_NAME="${ACR_NAME:-<acr>}" # From Phase 3
KEY_VAULT="${KEY_VAULT:-<keyvault>}" # From Phase 5
IDENTITY_ID="${IDENTITY_ID:?Set IDENTITY_ID from Phase 5}"
SECRET_URI=$(az keyvault secret show --vault-name "$KEY_VAULT" --name db-password --query id -o tsv)
az containerapp create --name spring-app --resource-group spring-rg --environment spring-env \
--image "${ACR_NAME}.azurecr.io/spring-app:v1.0" --target-port 8080 --ingress external \
--cpu 2.0 --memory 4Gi --min-replicas 2 --max-replicas 10 \
--user-assigned "$IDENTITY_ID" --registry-identity "$IDENTITY_ID" --registry-server "${ACR_NAME}.azurecr.io" \
--secrets db-password=keyvaultref:"${SECRET_URI}",identityref:"${IDENTITY_ID}" \
--env-vars SPRING_DATASOURCE_PASSWORD=secretref:db-password SPRING_PROFILES_ACTIVE=prod
```
**PowerShell:**
```powershell
$ACR_NAME = if ($env:ACR_NAME) { $env:ACR_NAME } else { "<acr>" } # From Phase 3
$KEY_VAULT = if ($env:KEY_VAULT) { $env:KEY_VAULT } else { "<keyvault>" } # From Phase 5
$IDENTITY_ID = if ($env:IDENTITY_ID) { $env:IDENTITY_ID } else { throw "Set IDENTITY_ID from Phase 5" }
$SECRET_URI = az keyvault secret show --vault-name "$KEY_VAULT" --name db-password --query id -o tsv
az containerapp create --name spring-app --resource-group spring-rg --environment spring-env `
--image "${ACR_NAME}.azurecr.io/spring-app:v1.0" --target-port 8080 --ingress external `
--cpu 2.0 --memory 4Gi --min-replicas 2 --max-replicas 10 `
--user-assigned "$IDENTITY_ID" --registry-identity "$IDENTITY_ID" --registry-server "${ACR_NAME}.azurecr.io" `
--secrets db-password=keyvaultref:"${SECRET_URI}",identityref:"${IDENTITY_ID}" `
--env-vars SPRING_DATASOURCE_PASSWORD=secretref:db-password SPRING_PROFILES_ACTIVE=prod
```
**With storage mount:** Export the app configuration, add volumeMounts, and update:
**Bash:**
```bash
az containerapp show --name spring-app --resource-group spring-rg -o yaml > app.yaml
# Edit app.yaml: add volumeMounts under containers[0] and volumes at template level
az containerapp update --name spring-app --resource-group spring-rg --yaml app.yaml
```
**PowerShell:**
```powershell
az containerapp show --name spring-app --resource-group spring-rg -o yaml | Out-File -Encoding utf8 app.yaml
# Edit app.yaml: add volumeMounts under containers[0] and volumes at template level
az containerapp update --name spring-app --resource-group spring-rg --yaml app.yaml
```
**Health Probes** (recommended for Spring Boot apps): Export configuration, add probes, and update:
**Bash:**
```bash
az containerapp show --name spring-app --resource-group spring-rg -o yaml > app.yaml
# Edit app.yaml: add probes under containers[0]
# probes:
# - type: Startup
# httpGet:
# path: /actuator/health
# port: 8080
# failureThreshold: 30
# periodSeconds: 2
# - type: Liveness
# httpGet:
# path: /actuator/health/liveness
# port: 8080
# - type: Readiness
# httpGet:
# path: /actuator/health/readiness
# port: 8080
az containerapp update --name spring-app --resource-group spring-rg --yaml app.yaml
```
**PowerShell:**
```powershell
az containerapp show --name spring-app --resource-group spring-rg -o yaml | Out-File -Encoding utf8 app.yaml
# Edit app.yaml: add probes under containers[0]
# probes:
# - type: Startup
# httpGet:
# path: /actuator/health
# port: 8080
# failureThreshold: 30
# periodSeconds: 2
# - type: Liveness
# httpGet:
# path: /actuator/health/liveness
# port: 8080
# - type: Readiness
# httpGet:
# path: /actuator/health/readiness
# port: 8080
az containerapp update --name spring-app --resource-group spring-rg --yaml app.yaml
```
## Phase 7: Validation
**Bash:**
```bash
FQDN=$(az containerapp show --name spring-app --resource-group spring-rg --query properties.configuration.ingress.fqdn -o tsv)
echo "Application URL: https://${FQDN}"
curl "https://${FQDN}/actuator/health"
az containerapp logs show --name spring-app --resource-group spring-rg --tail 50
```
**PowerShell:**
```powershell
$FQDN = az containerapp show --name spring-app --resource-group spring-rg --query properties.configuration.ingress.fqdn -o tsv
Write-Host "Application URL: https://${FQDN}"
Invoke-WebRequest "https://${FQDN}/actuator/health"
az containerapp logs show --name spring-app --resource-group spring-rg --tail 50
```
## Phase 8: Post-Migration Optimization
### Add Spring Cloud Config Server
**Bash:**
```bash
az containerapp env java-component config-server-for-spring create \
--environment spring-env --resource-group spring-rg --name config-server \
--min-replicas 1 --max-replicas 1 \
--configuration spring.cloud.config.server.git.uri=https://github.com/your-org/config-repo
az containerapp update --name spring-app --resource-group spring-rg --bind config-server
```
**PowerShell:**
```powershell
az containerapp env java-component config-server-for-spring create `
--environment spring-env --resource-group spring-rg --name config-server `
--min-replicas 1 --max-replicas 1 `
--configuration spring.cloud.config.server.git.uri=https://github.com/your-org/config-repo
az containerapp update --name spring-app --resource-group spring-rg --bind config-server
```
### Add Eureka Service Registry
**Bash:**
```bash
az containerapp env java-component eureka-server-for-spring create \
--environment spring-env --resource-group spring-rg --name eureka-server \
--min-replicas 1 --max-replicas 1
az containerapp update --name spring-app --resource-group spring-rg --bind eureka-server
```
**PowerShell:**
```powershell
az containerapp env java-component eureka-server-for-spring create `
--environment spring-env --resource-group spring-rg --name eureka-server `
--min-replicas 1 --max-replicas 1
az containerapp update --name spring-app --resource-group spring-rg --bind eureka-server
```
**Add dependency (pom.xml):**
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
```
### Add Spring Cloud Gateway
**Bash:**
```bash
az containerapp create --name spring-gateway --resource-group spring-rg --environment spring-env \
--image "${ACR_NAME}.azurecr.io/gateway:v1.0" --target-port 8080 --ingress external \
--cpu 1.0 --memory 2Gi --min-replicas 1 --max-replicas 5 \
--bind eureka-server config-server
```
**PowerShell:**
```powershell
az containerapp create --name spring-gateway --resource-group spring-rg --environment spring-env `
--image "${ACR_NAME}.azurecr.io/gateway:v1.0" --target-port 8080 --ingress external `
--cpu 1.0 --memory 2Gi --min-replicas 1 --max-replicas 5 `
--bind eureka-server config-server
```
### Add Spring Boot Admin
**Bash:**
```bash
az containerapp env java-component admin-for-spring create \
--environment spring-env --resource-group spring-rg --name admin-server \
--min-replicas 1 --max-replicas 1
az containerapp update --name spring-app --resource-group spring-rg --bind admin-server
```
**PowerShell:**
```powershell
az containerapp env java-component admin-for-spring create `
--environment spring-env --resource-group spring-rg --name admin-server `
--min-replicas 1 --max-replicas 1
az containerapp update --name spring-app --resource-group spring-rg --bind admin-server
```
## Troubleshooting
| Issue | Solution |
|-------|----------|
| Image pull fails | Verify ACR role: `az role assignment list --assignee $PRINCIPAL_ID --scope $ACR_ID` |
| App won't start | Check logs: `az containerapp logs show --name spring-app -g spring-rg --tail 100` |
| Health check fails | Verify port 8080 matches `server.port` in application.properties |
| Secrets not accessible | Check Key Vault policy: `az keyvault show --name $KEY_VAULT --query properties.accessPolicies` |
| Storage mount fails | Verify storage configuration: `az containerapp env storage list --name spring-env -g spring-rg` |
| High memory usage | Reduce max heap: add `--env-vars JAVA_OPTS="-Xmx2g"` to container app |
## CI/CD Integration
**GitHub Actions example:**
```yaml
- name: Build and push to ACR
run: |
az acr build --registry ${{ secrets.ACR_NAME }} --image spring-app:${{ github.sha }} .
- name: Deploy to Container Apps
run: |
az containerapp update --name spring-app -g spring-rg --image ${{ secrets.ACR_NAME }}.azurecr.io/spring-app:${{ github.sha }}
```
**Azure Pipelines example:**
```yaml
- task: AzureCLI@2
inputs:
azureSubscription: 'AzureConnection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az acr build --registry $(ACR_NAME) --image spring-app:$(Build.BuildId) .
az containerapp update --name spring-app -g spring-rg --image $(ACR_NAME).azurecr.io/spring-app:$(Build.BuildId)
```
references/services/functions/
assessment.md 6.3 KB
# Assessment Phase
Generate a migration assessment report before any code changes.
## Prerequisites
- Workspace contains AWS Lambda functions, SAM templates, or CloudFormation templates
- Prompt user to upload relevant files if not present
## Assessment Steps
1. **Identify Functions** — List all Lambda functions with runtimes, triggers, and dependencies
2. **Map AWS Services** — Map AWS services to Azure equivalents (see [lambda-to-functions.md](lambda-to-functions.md))
3. **Map Properties** — Map Lambda properties to Azure Functions properties
4. **Check Dependencies** — List 3rd-party libraries and verify Azure compatibility
5. **Analyze Code** — Check language support and runtime differences
6. **Map Triggers** — Identify equivalent Azure Functions triggers
7. **Map Deployment** — Identify equivalent Azure deployment strategies (CLI, Bicep, azd)
8. **Review CI/CD** — Check pipeline compatibility with Azure DevOps or GitHub Actions
9. **Map Monitoring** — Map CloudWatch → Application Insights / Azure Monitor
## Code Preview
During assessment, show a **sneak peek** of what the migrated Azure Functions code will look like for each function. Use bindings and triggers (not SDKs) in all previews, following Azure Functions best practices. **Always use the newest generally available (GA) language runtime listed in the Azure Functions supported languages documentation** in previews (for example, the latest Node.js LTS or newest Python GA version). This helps the user understand the migration scope before committing to code migration.
> ⚠️ **Binding-first rule**: Code previews MUST use `input.storageBlob()`, `output.storageBlob()`, `app.storageQueue()`, etc. instead of `BlobServiceClient`, `QueueClient`, or other SDK clients. Only use SDK for services that have no binding equivalent.
## Architecture Diagrams
Generate two diagrams:
1. **Current State** — AWS Lambda architecture with triggers and integrations
2. **Target State** — Azure Functions architecture showing equivalent structure
## Assessment Report Format
> ⚠️ **MANDATORY**: Use these exact section headings in every assessment report. Do NOT rename, reorder, or omit sections.
The report MUST be saved as `migration-assessment-report.md` inside the output directory (`<workspace-root-basename>-azure/`).
```markdown
# Migration Assessment Report
## 1. Executive Summary
| Property | Value |
|----------|-------|
| **Total Functions** | <count> |
| **Source Platform** | AWS Lambda |
| **Source Runtime** | <runtime and version> |
| **Target Platform** | Azure Functions |
| **Target Runtime** | <runtime and version> |
| **Migration Readiness** | <High / Medium / Low> |
| **Estimated Effort** | <Low / Medium / High> |
| **Assessment Date** | <date> |
## 2. Functions Inventory
| # | Function Name | Runtime | Trigger Type | Memory (MB) | Timeout (s) | Description |
|---|--------------|---------|-------------- |-------------|-------------|-------------|
| 1 | | | | | | |
## 3. Service Mapping
| AWS Service | Azure Equivalent | Migration Complexity | Notes |
|-------------|------------------|----------------------|-------|
| Lambda | Azure Functions | | |
| API Gateway | Azure Functions HTTP Trigger / APIM | | |
| S3 | Azure Blob Storage | | |
| DynamoDB | Cosmos DB | | |
| SQS | Service Bus / Storage Queue | | |
| SNS | Event Grid | | |
| CloudWatch | Application Insights / Azure Monitor | | |
| IAM Roles | Managed Identity + RBAC | | |
| CloudFormation / SAM | Bicep / ARM Templates | | |
## 4. Trigger & Binding Mapping
| # | Function Name | AWS Trigger | Azure Trigger | AWS Inputs/Outputs | Azure Bindings | Notes |
|---|--------------|-------------|---------------|--------------------| ---------------|-------|
| 1 | | | | | | |
## 5. Dependencies Analysis
| # | Package/Library | Version | AWS-Specific? | Azure Equivalent | Compatible? | Notes |
|---|----------------|---------|---------------|------------------|-------------|-------|
| 1 | | | | | | |
## 6. Environment Variables & Configuration
| # | AWS Variable | Purpose | Azure Equivalent | Auth Method | Notes |
|---|-------------|---------|------------------|-------------|-------|
| 1 | | | | Managed Identity / App Setting | |
## 7. Architecture Diagrams
### 7a. Current State (AWS)
<!-- Mermaid or ASCII diagram of AWS Lambda architecture -->
### 7b. Target State (Azure)
<!-- Mermaid or ASCII diagram of Azure Functions architecture -->
## 8. IAM & Security Mapping
| AWS IAM Role/Policy | Azure RBAC Role | Scope | Notes |
|---------------------|-----------------|-------|-------|
| | | | |
## 9. Monitoring & Observability Mapping
| AWS Service | Azure Equivalent | Migration Notes |
|-------------|------------------|-----------------|
| CloudWatch Logs | Application Insights | |
| CloudWatch Metrics | Azure Monitor Metrics | |
| CloudWatch Alarms | Azure Monitor Alerts | |
| X-Ray | Application Insights (distributed tracing) | |
## 10. CI/CD & Deployment Mapping
| AWS Tool | Azure Equivalent | Notes |
|----------|------------------|-------|
| SAM CLI | Azure Functions Core Tools / azd | |
| CloudFormation | Bicep / ARM Templates | |
| CodePipeline | Azure DevOps Pipelines / GitHub Actions | |
| CodeBuild | Azure DevOps Build / GitHub Actions | |
## 11. Project Structure Comparison
| AWS Lambda Structure | Azure Functions Structure |
|---------------------|--------------------------|
| `template.yaml` (SAM) | `host.json` |
| `handler.js / handler.py` | `src/app.js` / `src/function_app.py` |
| `requirements.txt` / `package.json` | `requirements.txt` / `package.json` |
| Per-function directories (optional) | Single entry point (v4 JS / v2 Python) |
| `event` object | Trigger-specific parameter |
| `context` object | `InvocationContext` |
## 12. Recommendations
1. **Runtime**: <recommended Azure Functions runtime and version>
2. **Hosting Plan**: <Flex Consumption / Premium>
3. **IaC Strategy**: <Bicep with azd / Terraform / ARM>
4. **Auth Strategy**: <Managed Identity for all service-to-service>
5. **Monitoring**: <Application Insights + Azure Monitor>
## 13. Next Steps
- [ ] Review and approve this assessment report
- [ ] Proceed to code migration (azure-cloud-migrate Phase 2)
- [ ] Hand off to azure-prepare for IaC generation
```
> 💡 **Tip:** Use `mcp_azure_mcp_get_azure_bestpractices` tool to learn Azure Functions project structure best practices for the comparison.
code-migration.md 5.9 KB
# Code Migration Phase
Migrate AWS Lambda function code to Azure Functions.
## Prerequisites
- Assessment report completed
- Azure Functions extension installed in VS Code
- Best practices loaded via `mcp_azure_mcp_get_azure_bestpractices` tool
## Rules
- If runtime is Python or Node.js: **do NOT create function.json files**
- If runtime is .NET (in-process or isolated) or Java: **do NOT hand-author function.json** — bindings metadata is generated from attributes/annotations at build time
- Use extension bundle version `[4.*, 5.0.0)` in host.json
- Use latest programming model (v4 for JavaScript, v2 for Python)
- **Always use bindings and triggers instead of SDKs** — For blob read/write, use `input.storageBlob()` / `output.storageBlob()` with `extraInputs`/`extraOutputs`. For queues, use `app.storageQueue()` or `app.serviceBusQueue()`. Only use SDK when there is no equivalent binding (e.g., Azure AI, custom HTTP calls)
- **Always use the latest supported language runtime** — Consult [supported languages](https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages) and select the newest GA version. Do NOT default to an older LTS version when a newer version is available on Azure Functions.
## Steps
1. **Install Azure Functions Extension** — Ensure VS Code extension is installed
2. **Load Best Practices** — Use `mcp_azure_mcp_get_azure_bestpractices` tool for code generation guidance
3. **Create Project Structure** — Set up the Azure Functions project inside the output directory (`<workspace-root-basename>-azure/`). Do NOT create files inside the original AWS directory
4. **Migrate Functions** — Convert each Lambda function to Azure Functions equivalent
5. **Update Dependencies** — Replace AWS SDKs with Azure SDKs in package.json / requirements.txt
6. **Configure Bindings** — Set up triggers and bindings inline (v4 JS / v2 Python)
7. **Configure Environment** — Map Lambda env vars to Azure Functions app settings
8. **Add Error Handling** — Ensure proper error handling in all functions
## Key Configuration Files
### host.json
```json
{
"version": "2.0",
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"extensions": {
"queues": {
"maxPollingInterval": "00:00:02",
"visibilityTimeout": "00:00:30",
"batchSize": 1,
"maxDequeueCount": 5
}
},
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
}
}
```
## Critical Infrastructure Dependencies
### Blob Trigger with EventGrid Source — Additional Requirements
When migrating S3 event triggers to Azure blob triggers with `source: 'EventGrid'`, the following infrastructure must be configured **at the IaC level** (not code level). Failure to set these up results in silent trigger failures.
| Requirement | Why | Consequence of Missing |
|------------|-----|----------------------|
| **Queue endpoint** (`AzureWebJobsStorage__queueServiceUri`) | Blob extension uses queues internally for poison-message tracking with EventGrid source | Function fails to index: "Unable to find matching constructor...QueueServiceClient" |
| **Always-ready instances** (Flex Consumption only) | Blob trigger group must be running to register the Event Grid webhook | Trigger group never starts → webhook never registered → events never delivered |
| **Event Grid subscription via Bicep/ARM** | CLI-based webhook validation handshake times out on Flex Consumption | Use `listKeys()` in Bicep to obtain the `blobs_extension` system key at deployment time |
| **Storage Queue Data Contributor** RBAC | Identity-based queue access for poison messages | 403 errors during blob trigger indexing |
See [lambda-to-functions.md](lambda-to-functions.md#flex-consumption--blob-trigger-with-eventgrid-source) for Bicep patterns.
### UAMI Credential Pattern
When using User Assigned Managed Identity (UAMI), `DefaultAzureCredential()` without arguments tries System Assigned first and fails. Always pass the client ID:
```javascript
const credential = new DefaultAzureCredential({
managedIdentityClientId: process.env.AZURE_CLIENT_ID
});
```
Add `AZURE_CLIENT_ID` as an app setting in Bicep pointing to the UAMI client ID.
### azd init Workaround for Non-Empty Directories
`azd init --template <template>` refuses to run in a non-empty directory. Use a temp-directory approach:
1. `azd init --template <template>` in an empty temp directory
2. Copy IaC files (`infra/`, `azure.yaml`, etc.) into the project root
3. Clean up the temp directory
### package.json (JavaScript)
```json
{
"dependencies": {
"@azure/functions": "^4.0.0",
"@azure/identity": "<latest>"
},
"devDependencies": {
"@azure/functions-core-tools": "^4",
"jest": "<latest>"
}
}
```
## Runtime-Specific Trigger & Binding Patterns
Load the appropriate runtime reference for the target language:
| Runtime | Reference |
|---------|----------|
| JavaScript (Node.js v4) | [runtimes/javascript.md](runtimes/javascript.md) |
| TypeScript (v4) | [runtimes/typescript.md](runtimes/typescript.md) |
| Python (v2) | [runtimes/python.md](runtimes/python.md) |
| C# (Isolated Worker) | [runtimes/csharp.md](runtimes/csharp.md) |
| Java | [runtimes/java.md](runtimes/java.md) |
| PowerShell | [runtimes/powershell.md](runtimes/powershell.md) |
## Scenario-Specific Guidance
See [lambda-to-functions.md](lambda-to-functions.md) for detailed trigger mapping, code patterns, and examples.
## Handoff to azure-prepare
After code migration is complete:
1. Update `migration-status.md` — mark Code Migration as ✅ Complete
2. Invoke **azure-prepare** — pass the assessment report context so it can:
- Use the service mapping as requirements input (skips manual gather-requirements)
- Generate IaC (Bicep/Terraform) for the mapped Azure services
- Create `azure.yaml` and `.azure/preparation-manifest.md`
- Apply security hardening
3. azure-prepare will then chain to **azure-validate** → **azure-deploy**
global-rules.md 3.3 KB
# Global Rules
These rules apply to ALL phases of the migration skill.
## Destructive Action Policy
⛔ **NEVER** perform destructive actions without explicit user confirmation via `ask_user`:
- Deleting files or directories
- Overwriting existing code
- Deploying to production environments
- Modifying existing Azure resources
- Removing AWS resources
## User Confirmation Required
Always use `ask_user` before:
- Selecting Azure subscription
- Selecting Azure region/location
- Deploying infrastructure
- Making breaking changes to existing code
## Best Practices
- Always use `mcp_azure_mcp_get_azure_bestpractices` tool before generating Azure code
- Prefer managed identity over connection strings
- **Always use the latest supported language runtime** — check [supported languages](https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages) for the newest GA version. Never default to older versions
- **Always prefer bindings over SDKs** — use `input.storageBlob()`, `output.storageBlob()`, `app.storageQueue()`, etc. instead of `BlobServiceClient`, `QueueClient`, or other SDK clients. Only use SDK when no binding exists for the service
- Follow Azure naming conventions
- Use Flex Consumption hosting plan for new Functions
## Identity-First Authentication (Zero API Keys)
> Enterprise subscriptions commonly enforce policies that block local auth. Always design for identity-based access from the start.
- **Storage accounts**: Set `allowSharedKeyAccess: false`. Use identity-based connections with `AzureWebJobsStorage__credential`, `__clientId`, and service-specific URIs (`__blobServiceUri`, `__queueServiceUri`, etc.)
- **Cognitive Services**: Set `disableLocalAuth: true`. Use UAMI + RBAC role (e.g., Cognitive Services User) instead of API keys
- **Application Insights**: Set `disableLocalAuth: true`. Use `APPLICATIONINSIGHTS_AUTHENTICATION_STRING` with `ClientId=<uamiClientId>;Authorization=AAD`
- **DefaultAzureCredential with UAMI**: When using User Assigned Managed Identity, always pass `managedIdentityClientId` explicitly:
```javascript
const credential = new DefaultAzureCredential({
managedIdentityClientId: process.env.AZURE_CLIENT_ID
});
```
Without this, `DefaultAzureCredential` tries SystemAssigned first and fails. Add `AZURE_CLIENT_ID` as an app setting mapped to the UAMI client ID.
## Flex Consumption Specifics
- **Always-ready for non-HTTP triggers**: Blob trigger groups on Flex Consumption require `alwaysReady: [{ name: "blob", instanceCount: 1 }]` to bootstrap the trigger listener. Without it, the trigger group never starts and Event Grid subscriptions are never auto-created (chicken-and-egg problem)
- **Blob trigger with EventGrid source requires queue endpoint**: The blob extension internally uses queues for poison-message tracking. Must include `AzureWebJobsStorage__queueServiceUri` even when using blob trigger (not queue trigger)
- **Event Grid subscriptions via Bicep/ARM only**: Do NOT create Event Grid event subscriptions via CLI — webhook validation fails on Flex Consumption with "response code Unknown". Deploy as Bicep resources using `listKeys()` to resolve the `blobs_extension` system key at deployment time
- **azd init on non-empty directories**: `azd init --template` refuses non-empty directories. Use temp directory approach: init in temp, copy template infrastructure files back
lambda-to-functions.md 10.2 KB
# AWS Lambda to Azure Functions Migration
Detailed guidance for migrating AWS Lambda functions to Azure Functions.
## Overview
| AWS Service | Azure Equivalent |
|-------------|------------------|
| Lambda | Azure Functions |
| API Gateway | Azure Functions HTTP Trigger / API Management |
| S3 | Azure Blob Storage |
| S3 Event | Azure Blob Storage + Event Grid |
| DynamoDB | Cosmos DB |
| SQS | Azure Service Bus / Storage Queue |
| SNS | Azure Event Grid |
| EventBridge | Azure Event Grid |
| CloudWatch | Application Insights / Azure Monitor |
| IAM Roles | Managed Identity + Azure RBAC |
| CloudFormation / SAM | Bicep / ARM Templates |
| Rekognition | Azure AI Computer Vision (Image Analysis) |
## Programming Model Mapping
| AWS Lambda | Azure Functions |
|------------|-----------------|
| `exports.handler` | `app.http()`, `app.storageBlob()`, etc. (v4) |
| `event` object | `request` / `blob` / trigger-specific param |
| `context` object | `context` (InvocationContext) |
| `callback` | Return value |
| `function.json` (v1-v3) | Inline bindings in code (v4 JS, v2 Python) |
## Trigger Mapping
| AWS Trigger | Azure Trigger | Notes |
|-------------|---------------|-------|
| API Gateway (REST/HTTP) | `app.http()` | Direct equivalent |
| S3 Event | `app.storageBlob()` | Use `source: 'EventGrid'` for reliability |
| SQS | `app.storageQueue()` or `app.serviceBusQueue()` | Service Bus for advanced scenarios |
| SNS | `app.eventGrid()` | Event Grid is push-based |
| EventBridge | `app.eventGrid()` | Map event patterns to filters |
| CloudWatch Events (Scheduled) | `app.timer()` | NCRONTAB expressions |
| DynamoDB Streams | Cosmos DB Change Feed trigger | Via `app.cosmosDB()` |
## Runtime-Specific Migration Patterns
For language-specific migration rules, correct/incorrect patterns, and code examples, see the runtime reference for the target language:
| Runtime | Migration Patterns |
|---------|-------------------|
| JavaScript (Node.js v4) | [runtimes/javascript.md — Lambda Migration Rules](runtimes/javascript.md#lambda-migration-rules) |
| Python (v2) | [runtimes/python.md — Lambda Migration Rules](runtimes/python.md#lambda-migration-rules) |
| TypeScript (v4) | [runtimes/typescript.md](runtimes/typescript.md) |
| C# (Isolated Worker) | [runtimes/csharp.md](runtimes/csharp.md) |
| Java | [runtimes/java.md](runtimes/java.md) |
| PowerShell | [runtimes/powershell.md](runtimes/powershell.md) |
## Project Structure
```
REQUIRED for Azure Functions:
src/
├── app.js (or function_app.py) # Main entry point
├── host.json # Function host configuration
├── local.settings.json # Local development settings
├── package.json (or requirements.txt)
├── [helper-modules] # Business logic
└── tests/ # Test files
❌ NEVER create:
├── [functionName]/ # No individual function directories
│ ├── function.json # No function.json (JS v4, Python v2)
│ └── index.js
```
## Environment Variables
```
✅ Use managed identity connections for Azure Functions storage:
AzureWebJobsStorage__blobServiceUri
AzureWebJobsStorage__queueServiceUri
AzureWebJobsStorage__tableServiceUri
✅ Use specific endpoint variables for other services:
COMPUTER_VISION_ENDPOINT
STORAGE_ACCOUNT_URL
SOURCE_CONTAINER_NAME
❌ Avoid:
CONNECTION_STRING (use managed identity)
API_KEY (use managed identity)
```
## Reference Links
- [AWS Lambda vs Azure Functions comparison](https://aka.ms/AWSLambda)
- [AWS to Azure services comparison](https://learn.microsoft.com/en-us/azure/architecture/aws-professional/)
- [Supported language runtimes](https://learn.microsoft.com/en-us/azure/azure-functions/supported-languages)
- [Triggers and bindings overview](https://learn.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings)
- [Functions quickstart JS (azd)](https://github.com/Azure-Samples/functions-quickstart-javascript-azd/tree/main/infra)
- [Functions quickstart .NET Event Grid (azd)](https://github.com/Azure-Samples/functions-quickstart-dotnet-azd-eventgrid-blob/tree/main/infra)
## Flex Consumption + Blob Trigger with EventGrid Source
> **⚠️ CRITICAL**: When deploying blob triggers with `source: 'EventGrid'` on Flex Consumption, there are three infrastructure requirements that are NOT automatically handled and will cause silent trigger failures if missed.
### 1. Always-Ready Instances (Bootstrap Problem)
On Flex Consumption, trigger groups only start when there's work to do. But the blob extension needs to be running to create the Event Grid subscription that would deliver work — a chicken-and-egg problem.
**Solution**: Configure `alwaysReady` for the blob trigger group in the function app's `scaleAndConcurrency`:
```bicep
// In api.bicep — functionAppConfig section
scaleAndConcurrency: {
alwaysReady: [
{
name: 'blob'
instanceCount: 1
}
]
instanceMemoryMB: 2048
maximumInstanceCount: 100
}
```
Without this, the trigger group never starts → Event Grid subscription never gets created → no events are delivered → function never triggers.
### 2. Queue Endpoint Required
The blob extension internally uses Storage Queues for poison-message tracking when `source: 'EventGrid'` is configured. Without the queue endpoint, the function fails to index with:
```
Unable to find matching constructor while trying to create an instance of QueueServiceClient.
Expected: serviceUri. Found: credential, clientId, blobServiceUri
```
**Solution**: Always enable the queue endpoint alongside blob when using EventGrid source:
```bicep
// In identity-based storage configuration
AzureWebJobsStorage__blobServiceUri: storageAccount.properties.primaryEndpoints.blob
AzureWebJobsStorage__queueServiceUri: storageAccount.properties.primaryEndpoints.queue // REQUIRED for EventGrid source
AzureWebJobsStorage__credential: 'managedidentity'
AzureWebJobsStorage__clientId: managedIdentityClientId
```
Also assign **Storage Queue Data Contributor** RBAC role to the UAMI.
### 3. Event Grid Subscription via Bicep (Not CLI)
Do **NOT** create Event Grid event subscriptions via CLI. The `az eventgrid system-topic event-subscription create` command requires a webhook validation handshake that consistently fails on Flex Consumption with "response code Unknown" (timeout during cold start).
**Solution**: Deploy the Event Grid system topic and event subscription as Bicep resources. ARM handles the webhook validation internally and reliably:
```bicep
// eventGrid.bicep
resource systemTopic 'Microsoft.EventGrid/systemTopics@2024-06-01-preview' = {
name: 'evgt-${storageAccountName}'
location: location
properties: {
source: storageAccount.id
topicType: 'Microsoft.Storage.StorageAccounts'
}
}
resource eventSubscription 'Microsoft.EventGrid/systemTopics/eventSubscriptions@2024-06-01-preview' = {
parent: systemTopic
name: 'blob-trigger-sub'
properties: {
destination: {
endpointType: 'WebHook'
properties: {
// ARM resolves system key and handles validation at deployment time
endpointUrl: 'https://${functionApp.properties.defaultHostName}/runtime/webhooks/blobs?functionName=${functionName}&code=${listKeys('${functionApp.id}/host/default', '2023-12-01').systemKeys.blobs_extension}'
}
}
filter: {
includedEventTypes: [ 'Microsoft.Storage.BlobCreated' ]
subjectBeginsWith: '/blobServices/default/containers/${sourceContainerName}/'
}
}
}
```
**RBAC requirement**: Assign **EventGrid EventSubscription Contributor** role to the UAMI.
## User Assigned Managed Identity (UAMI) Auth Patterns
### DefaultAzureCredential with UAMI
When using UAMI, `DefaultAzureCredential()` without arguments tries SystemAssigned first and fails. Always pass the client ID:
```javascript
const { DefaultAzureCredential } = require('@azure/identity');
const credential = new DefaultAzureCredential({
managedIdentityClientId: process.env.AZURE_CLIENT_ID
});
```
Add `AZURE_CLIENT_ID` as an app setting pointing to the UAMI client ID:
```bicep
appSettings: {
AZURE_CLIENT_ID: managedIdentity.outputs.clientId
}
```
### Identity-Linked Storage Connection
```bicep
AzureWebJobsStorage__credential: 'managedidentity'
AzureWebJobsStorage__clientId: managedIdentityClientId
AzureWebJobsStorage__blobServiceUri: storageAccount.properties.primaryEndpoints.blob
AzureWebJobsStorage__queueServiceUri: storageAccount.properties.primaryEndpoints.queue
```
### Required RBAC Roles for Face Blur Pattern
| Role | Scope | Purpose |
|------|-------|---------|
| Storage Blob Data Owner | Storage Account | Read source blobs, write destination blobs |
| Storage Queue Data Contributor | Storage Account | Poison-message queue for blob extension |
| EventGrid EventSubscription Contributor | Resource Group | Create/manage Event Grid subscriptions |
| Cognitive Services User | Cognitive Services Account | Call Computer Vision API |
| Monitoring Metrics Publisher | Application Insights | Emit telemetry |
## AWS Rekognition → Azure AI Computer Vision
| AWS | Azure |
|-----|-------|
| `@aws-sdk/client-rekognition` | `@azure-rest/ai-vision-image-analysis` |
| `DetectFaces` | Image Analysis `People` feature |
| `FaceDetails[].BoundingBox` (relative 0-1) | `peopleResult.values[].boundingBox` (pixel coordinates) |
> **⚠️ Package version**: `@azure-rest/ai-vision-image-analysis` is still in beta. The `^1.0.0` semver does NOT resolve. Pin explicitly: `"@azure-rest/ai-vision-image-analysis": "1.0.0-beta.3"`
### Coordinate Conversion
AWS Rekognition returns relative coordinates (0-1). Azure AI returns pixel coordinates. Convert for consistent face processing:
```javascript
const sharp = require('sharp');
const metadata = await sharp(imageBuffer).metadata();
const faces = result.body.peopleResult.values.map(person => ({
BoundingBox: {
Width: person.boundingBox.width / metadata.width,
Height: person.boundingBox.height / metadata.height,
Left: person.boundingBox.x / metadata.width,
Top: person.boundingBox.y / metadata.height
}
}));
```
### Auth: Use UAMI (No API Keys)
```bicep
// computerVision.bicep
resource computerVision 'Microsoft.CognitiveServices/accounts@2024-10-01' = {
kind: 'ComputerVision'
properties: {
disableLocalAuth: true // Enterprise policy compliance — no API keys
}
}
```
references/services/functions/runtimes/
csharp.md 5.5 KB
# C# — Azure Functions Isolated Worker Model Triggers & Bindings
> **Model**: .NET isolated worker model (recommended). Uses attributes on methods/parameters.
> Import: `using Microsoft.Azure.Functions.Worker;`
## HTTP Trigger
```csharp
[Function("HttpFunction")]
public static HttpResponseData Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
FunctionContext context)
{
var response = req.CreateResponse(HttpStatusCode.OK);
response.WriteString("Hello!");
return response;
}
```
## Blob Storage
```csharp
// Trigger (EventGrid source)
[Function("BlobTrigger")]
public static void Run(
[BlobTrigger("samples-workitems/{name}", Source = BlobTriggerSource.EventGrid,
Connection = "AzureWebJobsStorage")] string blobContent,
string name, FunctionContext context)
{
context.GetLogger("BlobTrigger").LogInformation($"Blob: {name}");
}
// Input
[BlobInput("input/{name}", Connection = "AzureWebJobsStorage")] string inputBlob
// Output
[BlobOutput("output/{name}", Connection = "AzureWebJobsStorage")] out string outputBlob
```
## Queue Storage
```csharp
// Trigger
[Function("QueueTrigger")]
public static void Run(
[QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string message,
FunctionContext context)
{
context.GetLogger("Queue").LogInformation($"Message: {message}");
}
// Output (via return type)
[Function("QueueOutput")]
[QueueOutput("outqueue", Connection = "AzureWebJobsStorage")]
public static string Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
return "queue message";
}
```
## Timer
```csharp
[Function("TimerFunction")]
public static void Run(
[TimerTrigger("0 */5 * * * *")] TimerInfo timer,
FunctionContext context)
{
context.GetLogger("Timer").LogInformation($"Last: {timer.ScheduleStatus?.Last}");
}
```
## Event Grid
```csharp
// Trigger
[Function("EventGridTrigger")]
public static void Run(
[EventGridTrigger] EventGridEvent eventGridEvent,
FunctionContext context)
{
context.GetLogger("EG").LogInformation($"Event: {eventGridEvent.Subject}");
}
// Output
[EventGridOutput(TopicEndpointUri = "MyTopicUri", TopicKeySetting = "MyTopicKey")]
```
## Cosmos DB
```csharp
// Trigger (Change Feed)
[Function("CosmosDBTrigger")]
public static void Run(
[CosmosDBTrigger("mydb", "mycontainer",
Connection = "CosmosDBConnection",
CreateLeaseContainerIfNotExists = true)] IReadOnlyList<MyDocument> documents,
FunctionContext context)
{
foreach (var doc in documents)
context.GetLogger("Cosmos").LogInformation($"Doc: {doc.Id}");
}
// Input
[CosmosDBInput("mydb", "mycontainer", Connection = "CosmosDBConnection",
Id = "{id}", PartitionKey = "{partitionKey}")] MyDocument doc
// Output
[CosmosDBOutput("mydb", "mycontainer", Connection = "CosmosDBConnection")]
```
## Service Bus
```csharp
// Queue Trigger
[Function("SBQueueTrigger")]
public static void Run(
[ServiceBusTrigger("myqueue", Connection = "ServiceBusConnection")] string message,
FunctionContext context)
{
context.GetLogger("SB").LogInformation($"Message: {message}");
}
// Topic Trigger
[Function("SBTopicTrigger")]
public static void Run(
[ServiceBusTrigger("mytopic", "mysubscription",
Connection = "ServiceBusConnection")] string message,
FunctionContext context)
{
context.GetLogger("SB").LogInformation($"Topic: {message}");
}
// Output
[ServiceBusOutput("outqueue", Connection = "ServiceBusConnection")]
```
## Event Hubs
```csharp
// Trigger
[Function("EventHubTrigger")]
public static void Run(
[EventHubTrigger("myeventhub", Connection = "EventHubConnection")] EventData[] events,
FunctionContext context)
{
foreach (var e in events)
context.GetLogger("EH").LogInformation($"Event: {e.EventBody}");
}
// Output
[EventHubOutput("outeventhub", Connection = "EventHubConnection")]
```
## Table Storage
```csharp
// Input
[TableInput("mytable", "{partitionKey}", "{rowKey}",
Connection = "AzureWebJobsStorage")] TableEntity entity
// Output
[TableOutput("mytable", Connection = "AzureWebJobsStorage")]
```
## SQL
```csharp
// Trigger
[Function("SqlTrigger")]
public static void Run(
[SqlTrigger("dbo.MyTable", "SqlConnection")] IReadOnlyList<SqlChange<MyItem>> changes,
FunctionContext context)
{
foreach (var change in changes)
context.GetLogger("SQL").LogInformation($"Change: {change.Item.Id}");
}
// Input
[SqlInput("SELECT * FROM dbo.MyTable WHERE Id = @Id",
"SqlConnection", parameters: "@Id={id}")] IEnumerable<MyItem> items
// Output
[SqlOutput("dbo.MyTable", "SqlConnection")]
```
## SignalR
```csharp
// Output
[SignalROutput(HubName = "myhub", ConnectionStringSetting = "AzureSignalRConnectionString")]
```
## SendGrid
```csharp
[SendGridOutput(ApiKey = "SendGridApiKey", From = "noreply@example.com")]
```
## Multiple Outputs
```csharp
// Use a custom return type for multiple outputs
public class MultiOutput
{
[QueueOutput("outqueue", Connection = "AzureWebJobsStorage")]
public string QueueMessage { get; set; }
public HttpResponseData HttpResponse { get; set; }
}
[Function("MultiOutput")]
public static MultiOutput Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")] HttpRequestData req)
{
return new MultiOutput
{
QueueMessage = "new item",
HttpResponse = req.CreateResponse(HttpStatusCode.OK)
};
}
```
> Full reference: [Azure Functions C# isolated worker guide](https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide)
java.md 6.4 KB
# Java — Azure Functions Triggers & Bindings
> **Model**: Java annotation-based model. Uses `@FunctionName` and trigger/binding annotations.
> Import: `com.microsoft.azure.functions.*` and `com.microsoft.azure.functions.annotation.*`
## HTTP Trigger
```java
@FunctionName("HttpFunction")
public HttpResponseMessage run(
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<Optional<String>> request,
final ExecutionContext context) {
String name = request.getQueryParameters().get("name");
return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
}
```
## Blob Storage
```java
// Trigger
@FunctionName("BlobTrigger")
public void run(
@BlobTrigger(name = "blob", path = "samples-workitems/{name}",
connection = "AzureWebJobsStorage") byte[] content,
@BindingName("name") String name,
final ExecutionContext context) {
context.getLogger().info("Blob: " + name + ", Size: " + content.length);
}
// Input
@BlobInput(name = "inputBlob", path = "input/{name}", connection = "AzureWebJobsStorage")
// Output
@BlobOutput(name = "outputBlob", path = "output/{name}-out", connection = "AzureWebJobsStorage")
```
## Queue Storage
```java
// Trigger
@FunctionName("QueueTrigger")
public void run(
@QueueTrigger(name = "message", queueName = "myqueue-items",
connection = "AzureWebJobsStorage") String message,
final ExecutionContext context) {
context.getLogger().info("Queue message: " + message);
}
// Output
@QueueOutput(name = "output", queueName = "outqueue", connection = "AzureWebJobsStorage")
```
## Timer
```java
@FunctionName("TimerFunction")
public void run(
@TimerTrigger(name = "timer", schedule = "0 */5 * * * *") String timerInfo,
final ExecutionContext context) {
context.getLogger().info("Timer triggered");
}
```
## Event Grid
```java
// Trigger
@FunctionName("EventGridTrigger")
public void run(
@EventGridTrigger(name = "event") EventGridEvent event,
final ExecutionContext context) {
context.getLogger().info("Event: " + event.subject());
}
// Output
@EventGridOutput(name = "output", topicEndpointUri = "MyTopicUri",
topicKeySetting = "MyTopicKey")
```
## Cosmos DB
```java
// Trigger (Change Feed)
@FunctionName("CosmosDBTrigger")
public void run(
@CosmosDBTrigger(name = "documents", databaseName = "mydb",
containerName = "mycontainer", connection = "CosmosDBConnection",
createLeaseContainerIfNotExists = true) String[] documents,
final ExecutionContext context) {
for (String doc : documents) context.getLogger().info("Doc: " + doc);
}
// Input
@CosmosDBInput(name = "doc", databaseName = "mydb", containerName = "mycontainer",
connection = "CosmosDBConnection", id = "{id}", partitionKey = "{pk}")
// Output
@CosmosDBOutput(name = "newdoc", databaseName = "mydb", containerName = "mycontainer",
connection = "CosmosDBConnection")
```
## Service Bus
```java
// Queue Trigger
@FunctionName("SBQueueTrigger")
public void run(
@ServiceBusQueueTrigger(name = "message", queueName = "myqueue",
connection = "ServiceBusConnection") String message,
final ExecutionContext context) {
context.getLogger().info("Message: " + message);
}
// Topic Trigger
@FunctionName("SBTopicTrigger")
public void run(
@ServiceBusTopicTrigger(name = "message", topicName = "mytopic",
subscriptionName = "mysubscription",
connection = "ServiceBusConnection") String message,
final ExecutionContext context) {
context.getLogger().info("Topic: " + message);
}
// Output
@ServiceBusQueueOutput(name = "output", queueName = "outqueue",
connection = "ServiceBusConnection")
```
## Event Hubs
```java
// Trigger
@FunctionName("EventHubTrigger")
public void run(
@EventHubTrigger(name = "event", eventHubName = "myeventhub",
connection = "EventHubConnection", cardinality = Cardinality.MANY) List<String> events,
final ExecutionContext context) {
events.forEach(e -> context.getLogger().info("Event: " + e));
}
// Output
@EventHubOutput(name = "output", eventHubName = "outeventhub",
connection = "EventHubConnection")
```
## Table Storage
```java
// Input
@TableInput(name = "entity", tableName = "mytable", partitionKey = "{pk}",
rowKey = "{rk}", connection = "AzureWebJobsStorage")
// Output
@TableOutput(name = "output", tableName = "mytable", connection = "AzureWebJobsStorage")
```
## SQL
```java
// Trigger
@FunctionName("SqlTrigger")
public void run(
@SqlTrigger(name = "changes", tableName = "dbo.MyTable",
connectionStringSetting = "SqlConnection") SqlChange[] changes,
final ExecutionContext context) {
for (SqlChange change : changes) context.getLogger().info("Change: " + change);
}
// Input
@SqlInput(name = "items", commandText = "SELECT * FROM dbo.MyTable WHERE Id = @Id",
commandType = "Text", parameters = "@Id={id}",
connectionStringSetting = "SqlConnection")
// Output
@SqlOutput(name = "output", commandText = "dbo.MyTable",
connectionStringSetting = "SqlConnection")
```
## SignalR
```java
@SignalROutput(name = "signalr", hubName = "myhub",
connectionStringSetting = "AzureSignalRConnectionString")
```
## SendGrid
```java
@SendGridOutput(name = "mail", apiKey = "SendGridApiKey",
from = "noreply@example.com", to = "{email}")
```
## Multiple Outputs
Java uses `OutputBinding<T>` for additional outputs:
```java
@FunctionName("MultiOutput")
public HttpResponseMessage run(
@HttpTrigger(name = "req", methods = {HttpMethod.POST},
authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage<String> request,
@QueueOutput(name = "output", queueName = "outqueue",
connection = "AzureWebJobsStorage") OutputBinding<String> queueOutput,
final ExecutionContext context) {
queueOutput.setValue("new item");
return request.createResponseBuilder(HttpStatus.OK).body("Processed").build();
}
```
> **Note**: Java uses `function.json` generated at build time by the Maven plugin — you don't write them manually.
> Full reference: [Azure Functions Java developer guide](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java)
javascript.md 8.5 KB
# JavaScript (Node.js) — Azure Functions v4 Triggers & Bindings
> **Model**: JavaScript v4 programming model. **NO** `function.json` files.
> Import: `const { app, input, output } = require('@azure/functions');`
## Lambda Migration Rules
> Shared rules (bindings over SDKs, latest runtime, identity-first auth) → [global-rules.md](../global-rules.md)
JS-specific:
- Use `extraInputs` / `extraOutputs` with binding path expressions (e.g., `{queueTrigger}`) for dynamic blob I/O
- Access metadata via `context.triggerMetadata`
- `package.json`: `"@azure/functions": "^4.0.0"`
### Correct Migration Pattern
```javascript
const { app, input, output } = require('@azure/functions');
// Use bindings for blob I/O instead of BlobServiceClient SDK
const blobInput = input.storageBlob({
path: 'source-container/{queueTrigger}',
connection: 'AzureWebJobsStorage'
});
const blobOutput = output.storageBlob({
path: 'destination-container/{queueTrigger}',
connection: 'AzureWebJobsStorage'
});
app.storageQueue('processImage', {
queueName: 'image-processing',
connection: 'AzureWebJobsStorage',
extraInputs: [blobInput],
extraOutputs: [blobOutput],
handler: async (queueItem, context) => {
const sourceBlob = context.extraInputs.get(blobInput);
context.log(`Processing blob: ${queueItem}`);
// Process the blob...
context.extraOutputs.set(blobOutput, processedBuffer);
}
});
```
> ❌ Do NOT use legacy v1-v3 `module.exports` — always use `app.*()` registration.
## HTTP Trigger
```javascript
app.http('httpFunction', {
methods: ['GET', 'POST'],
authLevel: 'anonymous',
handler: async (request, context) => {
const name = request.query.get('name') || (await request.text());
return { body: `Hello, ${name}!` };
}
});
```
## Blob Storage
```javascript
// Trigger (use EventGrid source for reliability)
app.storageBlob('blobTrigger', {
path: 'samples-workitems/{name}',
connection: 'AzureWebJobsStorage',
source: 'EventGrid',
handler: async (blob, context) => {
context.log(`Blob: ${context.triggerMetadata.name}, Size: ${blob.length}`);
}
});
// Input binding
const blobInput = input.storageBlob({
path: 'samples-workitems/{queueTrigger}',
connection: 'AzureWebJobsStorage'
});
// Output binding
const blobOutput = output.storageBlob({
path: 'samples-output/{name}-out',
connection: 'AzureWebJobsStorage'
});
```
> **⚠️ Flex Consumption + EventGrid Source Requirements:**
> When using `source: 'EventGrid'` on a Flex Consumption plan, three infrastructure requirements MUST be met or the trigger will silently fail:
>
> 1. **Always-ready instances**: Configure `alwaysReady: [{ name: 'blob', instanceCount: 1 }]` in Bicep. Without this, the trigger group never starts and the Event Grid webhook endpoint is never registered.
> 2. **Queue endpoint**: Set `AzureWebJobsStorage__queueServiceUri` in app settings. The blob extension uses queues internally for poison-message tracking with EventGrid source, even though you're not using a queue trigger.
> 3. **Event Grid subscription via Bicep/ARM**: Do NOT create event subscriptions via CLI — webhook validation times out on Flex Consumption. Deploy as a Bicep resource using `listKeys()` to obtain the `blobs_extension` system key.
>
> See [lambda-to-functions.md](../lambda-to-functions.md#flex-consumption--blob-trigger-with-eventgrid-source) for full Bicep patterns.
### Using Azure AI Services with UAMI
When calling Azure AI services (Computer Vision, etc.) from a function, use `DefaultAzureCredential` with explicit UAMI client ID:
```javascript
const { DefaultAzureCredential } = require('@azure/identity');
const createClient = require('@azure-rest/ai-vision-image-analysis').default;
const credential = new DefaultAzureCredential({
managedIdentityClientId: process.env.AZURE_CLIENT_ID // Required for UAMI
});
const client = createClient(process.env.COMPUTER_VISION_ENDPOINT, credential);
const result = await client.path('/imageanalysis:analyze').post({
body: { url: blobUrl },
queryParameters: { features: ['People'] } // Use 'People' for face detection
});
```
> **Note**: `@azure-rest/ai-vision-image-analysis` is still in beta. Pin explicitly: `"1.0.0-beta.3"` — the `^1.0.0` semver range does NOT resolve.
## Queue Storage
```javascript
// Trigger
app.storageQueue('queueTrigger', {
queueName: 'myqueue-items',
connection: 'AzureWebJobsStorage',
handler: async (queueItem, context) => {
context.log('Queue item:', queueItem);
}
});
// Output
const queueOutput = output.storageQueue({
queueName: 'outqueue',
connection: 'AzureWebJobsStorage'
});
```
## Timer
```javascript
app.timer('timerFunction', {
schedule: '0 */5 * * * *', // Every 5 minutes (NCRONTAB)
handler: async (myTimer, context) => {
context.log('Timer fired at:', myTimer.scheduleStatus.last);
}
});
```
## Event Grid
```javascript
// Trigger
app.eventGrid('eventGridTrigger', {
handler: async (event, context) => {
context.log('Event:', event.subject, event.eventType);
}
});
// Output
const eventGridOutput = output.eventGrid({
topicEndpointUri: 'MyEventGridTopicUriSetting',
topicKeySetting: 'MyEventGridTopicKeySetting'
});
```
## Cosmos DB
```javascript
// Trigger (Change Feed)
app.cosmosDB('cosmosDBTrigger', {
connectionStringSetting: 'CosmosDBConnection',
databaseName: 'mydb',
containerName: 'mycontainer',
createLeaseContainerIfNotExists: true,
handler: async (documents, context) => {
documents.forEach(doc => context.log('Changed doc:', doc.id));
}
});
// Input
const cosmosInput = input.cosmosDB({
connectionStringSetting: 'CosmosDBConnection',
databaseName: 'mydb',
containerName: 'mycontainer',
id: '{id}',
partitionKey: '{partitionKey}'
});
// Output
const cosmosOutput = output.cosmosDB({
connectionStringSetting: 'CosmosDBConnection',
databaseName: 'mydb',
containerName: 'mycontainer'
});
```
## Service Bus
```javascript
// Queue Trigger
app.serviceBusQueue('sbQueueTrigger', {
queueName: 'myqueue',
connection: 'ServiceBusConnection',
handler: async (message, context) => {
context.log('Message:', message);
}
});
// Topic Trigger
app.serviceBusTopic('sbTopicTrigger', {
topicName: 'mytopic',
subscriptionName: 'mysubscription',
connection: 'ServiceBusConnection',
handler: async (message, context) => {
context.log('Topic message:', message);
}
});
// Output
const sbOutput = output.serviceBusQueue({
queueName: 'outqueue',
connection: 'ServiceBusConnection'
});
```
## Event Hubs
```javascript
// Trigger
app.eventHub('eventHubTrigger', {
eventHubName: 'myeventhub',
connection: 'EventHubConnection',
cardinality: 'many',
handler: async (events, context) => {
events.forEach(event => context.log('Event:', event));
}
});
// Output
const ehOutput = output.eventHub({
eventHubName: 'outeventhub',
connection: 'EventHubConnection'
});
```
## Table Storage
```javascript
// Input
const tableInput = input.table({
tableName: 'mytable',
partitionKey: '{partitionKey}',
rowKey: '{rowKey}',
connection: 'AzureWebJobsStorage'
});
// Output
const tableOutput = output.table({
tableName: 'mytable',
connection: 'AzureWebJobsStorage'
});
```
## SQL
```javascript
// Trigger
app.generic('sqlTrigger', {
trigger: { type: 'sqlTrigger', tableName: 'dbo.MyTable', connectionStringSetting: 'SqlConnection' },
handler: async (changes, context) => {
changes.forEach(change => context.log('Change:', change));
}
});
// Input
const sqlInput = input.sql({
commandText: 'SELECT * FROM dbo.MyTable WHERE Id = @Id',
commandType: 'Text',
parameters: '@Id={id}',
connectionStringSetting: 'SqlConnection'
});
// Output
const sqlOutput = output.sql({
commandText: 'dbo.MyTable',
connectionStringSetting: 'SqlConnection'
});
```
## SignalR
```javascript
// Output
const signalROutput = output.generic({
type: 'signalR',
hubName: 'myhub',
connectionStringSetting: 'AzureSignalRConnectionString'
});
```
## SendGrid
```javascript
const sendGridOutput = output.generic({
type: 'sendGrid',
apiKey: 'SendGridApiKey',
from: 'noreply@example.com',
to: '{email}'
});
```
## Using Bindings with Functions
```javascript
// Combine trigger with input/output bindings
app.http('processItem', {
methods: ['POST'],
extraInputs: [cosmosInput],
extraOutputs: [queueOutput],
handler: async (request, context) => {
const doc = context.extraInputs.get(cosmosInput);
context.extraOutputs.set(queueOutput, JSON.stringify(doc));
return { body: 'Processed' };
}
});
```
> Full reference: [Azure Functions JavaScript developer guide](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node)
powershell.md 4.9 KB
# PowerShell — Azure Functions Triggers & Bindings
> **Model**: PowerShell uses `function.json` for binding definitions + `run.ps1` for handler code.
> Each function lives in its own directory: `<FunctionName>/function.json` + `run.ps1`
## Project Structure
```
src/
├── host.json
├── local.settings.json
├── requirements.psd1
├── profile.ps1
├── HttpFunction/
│ ├── function.json
│ └── run.ps1
└── TimerFunction/
├── function.json
└── run.ps1
```
## HTTP Trigger
**function.json:**
```json
{
"bindings": [
{ "authLevel": "anonymous", "type": "httpTrigger", "direction": "in",
"name": "Request", "methods": ["get", "post"] },
{ "type": "http", "direction": "out", "name": "Response" }
]
}
```
**run.ps1:**
```powershell
param($Request, $TriggerMetadata)
$name = $Request.Query.Name
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = "Hello, $name!"
})
```
## Blob Storage
**function.json:**
```json
{
"bindings": [
{ "name": "InputBlob", "type": "blobTrigger", "direction": "in",
"path": "samples-workitems/{name}", "connection": "AzureWebJobsStorage" },
{ "name": "OutputBlob", "type": "blob", "direction": "out",
"path": "output/{name}", "connection": "AzureWebJobsStorage" }
]
}
```
**run.ps1:**
```powershell
param($InputBlob, $TriggerMetadata)
Write-Host "Blob: $($TriggerMetadata.Name), Size: $($InputBlob.Length)"
Push-OutputBinding -Name OutputBlob -Value $InputBlob
```
## Queue Storage
**function.json:**
```json
{
"bindings": [
{ "name": "QueueItem", "type": "queueTrigger", "direction": "in",
"queueName": "myqueue-items", "connection": "AzureWebJobsStorage" },
{ "name": "OutputQueue", "type": "queue", "direction": "out",
"queueName": "outqueue", "connection": "AzureWebJobsStorage" }
]
}
```
**run.ps1:**
```powershell
param($QueueItem, $TriggerMetadata)
Write-Host "Queue message: $QueueItem"
Push-OutputBinding -Name OutputQueue -Value "Processed: $QueueItem"
```
## Timer
**function.json:**
```json
{
"bindings": [
{ "name": "Timer", "type": "timerTrigger", "direction": "in",
"schedule": "0 */5 * * * *" }
]
}
```
**run.ps1:**
```powershell
param($Timer)
Write-Host "Timer triggered at: $(Get-Date)"
```
## Event Grid
**function.json:**
```json
{
"bindings": [
{ "name": "EventGridEvent", "type": "eventGridTrigger", "direction": "in" }
]
}
```
**run.ps1:**
```powershell
param($EventGridEvent, $TriggerMetadata)
Write-Host "Event: $($EventGridEvent.subject) - $($EventGridEvent.eventType)"
```
## Cosmos DB
**function.json:**
```json
{
"bindings": [
{ "name": "Documents", "type": "cosmosDBTrigger", "direction": "in",
"connectionStringSetting": "CosmosDBConnection", "databaseName": "mydb",
"containerName": "mycontainer", "createLeaseContainerIfNotExists": true },
{ "name": "OutputDoc", "type": "cosmosDB", "direction": "out",
"connectionStringSetting": "CosmosDBConnection", "databaseName": "mydb",
"containerName": "outcontainer" }
]
}
```
**run.ps1:**
```powershell
param($Documents, $TriggerMetadata)
foreach ($doc in $Documents) {
Write-Host "Changed doc: $($doc.id)"
}
```
## Service Bus
**function.json:**
```json
{
"bindings": [
{ "name": "Message", "type": "serviceBusTrigger", "direction": "in",
"queueName": "myqueue", "connection": "ServiceBusConnection" },
{ "name": "OutputMessage", "type": "serviceBus", "direction": "out",
"queueName": "outqueue", "connection": "ServiceBusConnection" }
]
}
```
**run.ps1:**
```powershell
param($Message, $TriggerMetadata)
Write-Host "Message: $Message"
Push-OutputBinding -Name OutputMessage -Value "Processed: $Message"
```
## Event Hubs
**function.json:**
```json
{
"bindings": [
{ "name": "Events", "type": "eventHubTrigger", "direction": "in",
"eventHubName": "myeventhub", "connection": "EventHubConnection",
"cardinality": "many" }
]
}
```
## Table Storage
**function.json:**
```json
{
"bindings": [
{ "name": "TableEntity", "type": "table", "direction": "in",
"tableName": "mytable", "partitionKey": "{pk}", "rowKey": "{rk}",
"connection": "AzureWebJobsStorage" },
{ "name": "TableOut", "type": "table", "direction": "out",
"tableName": "mytable", "connection": "AzureWebJobsStorage" }
]
}
```
## SQL
**function.json:**
```json
{
"bindings": [
{ "name": "Changes", "type": "sqlTrigger", "direction": "in",
"tableName": "dbo.MyTable", "connectionStringSetting": "SqlConnection" }
]
}
```
## SendGrid
**function.json:**
```json
{
"bindings": [
{ "name": "Mail", "type": "sendGrid", "direction": "out",
"apiKey": "SendGridApiKey", "from": "noreply@example.com" }
]
}
```
> **Note**: PowerShell always requires `function.json` — it does not support inline binding definitions.
> Full reference: [Azure Functions PowerShell developer guide](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-powershell)
python.md 6.4 KB
# Python — Azure Functions v2 Triggers & Bindings
> **Model**: Python v2 programming model. **NO** `function.json` files.
> Entry point: `function_app.py`
> Import: `import azure.functions as func`
## Lambda Migration Rules
> Shared rules (bindings over SDKs, latest runtime, identity-first auth) → [global-rules.md](../global-rules.md)
Python-specific: all functions use the v2 decorator model shown throughout this file. No additional migration rules beyond global.
## HTTP Trigger
```python
@app.route(route="hello", methods=["GET", "POST"], auth_level=func.AuthLevel.ANONYMOUS)
def http_function(req: func.HttpRequest) -> func.HttpResponse:
name = req.params.get('name') or req.get_body().decode()
return func.HttpResponse(f"Hello, {name}!")
```
## Blob Storage
```python
# Trigger (use EventGrid source)
@app.blob_trigger(arg_name="myblob", path="samples-workitems/{name}",
connection="AzureWebJobsStorage", source="EventGrid")
def blob_trigger(myblob: func.InputStream):
logging.info(f"Blob: {myblob.name}, Size: {myblob.length}")
# Input
@app.blob_input(arg_name="inputblob", path="input/{name}", connection="AzureWebJobsStorage")
# Output
@app.blob_output(arg_name="outputblob", path="output/{name}", connection="AzureWebJobsStorage")
```
## Queue Storage
```python
# Trigger
@app.queue_trigger(arg_name="msg", queue_name="myqueue-items",
connection="AzureWebJobsStorage")
def queue_trigger(msg: func.QueueMessage):
logging.info(f"Queue message: {msg.get_body().decode()}")
# Output
@app.queue_output(arg_name="outputmsg", queue_name="outqueue",
connection="AzureWebJobsStorage")
```
## Timer
```python
@app.timer_trigger(schedule="0 */5 * * * *", arg_name="mytimer",
run_on_startup=False)
def timer_function(mytimer: func.TimerRequest):
logging.info("Timer triggered")
```
## Event Grid
```python
# Trigger
@app.event_grid_trigger(arg_name="event")
def eventgrid_trigger(event: func.EventGridEvent):
logging.info(f"Event: {event.subject} - {event.event_type}")
# Output
@app.event_grid_output(arg_name="outputEvent",
topic_endpoint_uri="MyEventGridTopicUriSetting",
topic_key_setting="MyEventGridTopicKeySetting")
```
## Cosmos DB
```python
# Trigger (Change Feed)
@app.cosmos_db_trigger_v3(arg_name="documents",
connection="CosmosDBConnection",
database_name="mydb",
container_name="mycontainer",
create_lease_container_if_not_exists=True)
def cosmosdb_trigger(documents: func.DocumentList):
for doc in documents:
logging.info(f"Changed doc: {doc['id']}")
# Input
@app.cosmos_db_input(arg_name="doc", connection="CosmosDBConnection",
database_name="mydb", container_name="mycontainer",
id="{id}", partition_key="{partitionKey}")
# Output
@app.cosmos_db_output(arg_name="newdoc", connection="CosmosDBConnection",
database_name="mydb", container_name="mycontainer")
```
## Service Bus
```python
# Queue Trigger
@app.service_bus_queue_trigger(arg_name="msg", queue_name="myqueue",
connection="ServiceBusConnection")
def sb_queue_trigger(msg: func.ServiceBusMessage):
logging.info(f"Message: {msg.get_body().decode()}")
# Topic Trigger
@app.service_bus_topic_trigger(arg_name="msg", topic_name="mytopic",
subscription_name="mysubscription",
connection="ServiceBusConnection")
def sb_topic_trigger(msg: func.ServiceBusMessage):
logging.info(f"Topic message: {msg.get_body().decode()}")
# Output
@app.service_bus_queue_output(arg_name="outmsg", queue_name="outqueue",
connection="ServiceBusConnection")
```
## Event Hubs
```python
# Trigger
@app.event_hub_message_trigger(arg_name="event", event_hub_name="myeventhub",
connection="EventHubConnection",
cardinality="many")
def eventhub_trigger(event: func.EventHubEvent):
logging.info(f"Event: {event.get_body().decode()}")
# Output
@app.event_hub_output(arg_name="outevent", event_hub_name="outeventhub",
connection="EventHubConnection")
```
## Table Storage
```python
# Input
@app.table_input(arg_name="tableEntity", table_name="mytable",
partition_key="{partitionKey}", row_key="{rowKey}",
connection="AzureWebJobsStorage")
# Output
@app.table_output(arg_name="tableOut", table_name="mytable",
connection="AzureWebJobsStorage")
```
## SQL
```python
# Trigger
@app.sql_trigger(arg_name="changes", table_name="dbo.MyTable",
connection_string_setting="SqlConnection")
def sql_trigger(changes: func.SqlRowList):
for change in changes:
logging.info(f"Change: {change}")
# Input
@app.sql_input(arg_name="items",
command_text="SELECT * FROM dbo.MyTable WHERE Id = @Id",
command_type="Text", parameters="@Id={id}",
connection_string_setting="SqlConnection")
# Output
@app.sql_output(arg_name="newitem", command_text="dbo.MyTable",
connection_string_setting="SqlConnection")
```
## SignalR
```python
@app.generic_output_binding(arg_name="signalr", type="signalR",
hub_name="myhub",
connection_string_setting="AzureSignalRConnectionString")
```
## SendGrid
```python
@app.generic_output_binding(arg_name="mail", type="sendGrid",
api_key="SendGridApiKey",
from_address="noreply@example.com")
```
## Combining Decorators
```python
@app.route(route="process", methods=["POST"])
@app.cosmos_db_input(arg_name="doc", connection="CosmosDBConnection",
database_name="mydb", container_name="mycontainer",
id="{id}", partition_key="{pk}")
@app.queue_output(arg_name="outmsg", queue_name="outqueue",
connection="AzureWebJobsStorage")
def process_item(req: func.HttpRequest, doc: func.DocumentList,
outmsg: func.Out[str]) -> func.HttpResponse:
outmsg.set(doc[0].to_json())
return func.HttpResponse("Processed")
```
> Full reference: [Azure Functions Python developer guide](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python)
typescript.md 3.5 KB
# TypeScript — Azure Functions v4 Triggers & Bindings
> **Model**: Node.js v4 programming model (TypeScript). Same as JavaScript v4 with type annotations.
> **NO** `function.json` files.
> Import: `import { app, HttpRequest, HttpResponseInit, InvocationContext, input, output } from '@azure/functions';`
TypeScript uses the same `app.*()` registration as JavaScript. See [javascript.md](javascript.md) for all trigger/binding patterns. Below are TypeScript-specific type signatures.
## HTTP Trigger
```typescript
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
app.http('httpFunction', {
methods: ['GET', 'POST'],
authLevel: 'anonymous',
handler: async (request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> => {
const name = request.query.get('name') || (await request.text());
return { body: `Hello, ${name}!` };
}
});
```
## Blob Storage Trigger
```typescript
import { app, InvocationContext } from '@azure/functions';
app.storageBlob('blobTrigger', {
path: 'samples-workitems/{name}',
connection: 'AzureWebJobsStorage',
source: 'EventGrid',
handler: async (blob: Buffer, context: InvocationContext): Promise<void> => {
context.log(`Blob: ${context.triggerMetadata.name}, Size: ${blob.length}`);
}
});
```
## Queue Storage Trigger
```typescript
app.storageQueue('queueTrigger', {
queueName: 'myqueue-items',
connection: 'AzureWebJobsStorage',
handler: async (queueItem: unknown, context: InvocationContext): Promise<void> => {
context.log('Queue item:', queueItem);
}
});
```
## Timer Trigger
```typescript
import { app, InvocationContext, Timer } from '@azure/functions';
app.timer('timerFunction', {
schedule: '0 */5 * * * *',
handler: async (myTimer: Timer, context: InvocationContext): Promise<void> => {
context.log('Timer fired at:', myTimer.scheduleStatus?.last);
}
});
```
## Cosmos DB Trigger
```typescript
app.cosmosDB('cosmosDBTrigger', {
connectionStringSetting: 'CosmosDBConnection',
databaseName: 'mydb',
containerName: 'mycontainer',
createLeaseContainerIfNotExists: true,
handler: async (documents: unknown[], context: InvocationContext): Promise<void> => {
documents.forEach(doc => context.log('Changed doc:', doc));
}
});
```
## Service Bus Queue Trigger
```typescript
app.serviceBusQueue('sbQueueTrigger', {
queueName: 'myqueue',
connection: 'ServiceBusConnection',
handler: async (message: unknown, context: InvocationContext): Promise<void> => {
context.log('Message:', message);
}
});
```
## Event Grid Trigger
```typescript
import { app, EventGridEvent, InvocationContext } from '@azure/functions';
app.eventGrid('eventGridTrigger', {
handler: async (event: EventGridEvent, context: InvocationContext): Promise<void> => {
context.log('Event:', event.subject, event.eventType);
}
});
```
## Event Hubs Trigger
```typescript
app.eventHub('eventHubTrigger', {
eventHubName: 'myeventhub',
connection: 'EventHubConnection',
cardinality: 'many',
handler: async (events: unknown[], context: InvocationContext): Promise<void> => {
events.forEach(event => context.log('Event:', event));
}
});
```
## Input/Output Bindings
TypeScript uses the same `input.*()` and `output.*()` helpers as JavaScript. See [javascript.md](javascript.md) for full binding examples — all patterns are identical with added type annotations.
> Full reference: [Azure Functions TypeScript developer guide](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=typescript)
references/
workflow-details.md 2.3 KB
# Workflow Details
## Status Tracking
Maintain a `migration-status.md` file in the output directory (`<workspace-root-basename>-azure/`):
```markdown
# Migration Status
| Phase | Status | Notes |
|-------|--------|-------|
| Assessment | ⬜ Not Started | |
| Code Migration | ⬜ Not Started | |
```
Update status: ⬜ Not Started → 🔄 In Progress → ✅ Complete → ❌ Failed
## User Progress Updates
During long-running operations (Azure deployments, image pushes, environment provisioning), **proactively report progress** so the user is never left waiting without feedback:
1. **Resource-level status table** — After submitting a deployment, poll `az resource list` or `az deployment operation group list` and present a status table:
```
| Resource | Status |
|----------|--------|
| VNet | ✅ Created |
| ACR | ✅ Created |
| Container Apps Env | 🔄 Provisioning |
| order-service | ⬜ Waiting |
```
2. **Explain what's slow** — If a resource takes >2 minutes (e.g., Container Apps Environment with VNet), tell the user *why* ("VNet integration provisions internal load balancers and DNS — this typically takes 3-5 min").
3. **Don't go silent** — If a single `az deployment group create` covers all resources, poll `az resource list -g <rg>` periodically and update the user on newly created resources.
4. **Announce each phase transition** — When moving between skill phases (assess → migrate → deploy → validate), clearly tell the user what just completed and what's next.
## Error Handling
| Error | Cause | Remediation |
|-------|-------|-------------|
| Unsupported runtime | Source runtime not available in target Azure service | Check target service's supported languages documentation |
| Missing service mapping | Source service has no direct Azure equivalent | Use closest Azure alternative, document in assessment |
| Code migration failure | Incompatible patterns or dependencies | Review scenario-specific guide in [lambda-to-functions.md](services/functions/lambda-to-functions.md) |
| `azd init` refuses non-empty directory | azd requires clean directory for template init | Use temp directory approach: init in empty dir, copy files back |
> For scenario-specific errors (e.g., Azure Functions binding issues, trigger configuration), see the error table in the corresponding scenario reference.
License (MIT)
View full license text
MIT License Copyright 2025 (c) Microsoft Corporation. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.