⏱️ Estimated Time: 25-35 minutes | 💰 Estimated Cost: ~$50-100/month | ⭐ Complexity: Advanced
A simplified but functional microservices architecture deployed to Azure Container Apps using AZD CLI. This example demonstrates service-to-service communication, container orchestration, and monitoring with a practical 2-service setup.
📚 Learning Approach: This example starts with a minimal 2-service architecture (API Gateway + Backend Service) that you can actually deploy and learn from. After mastering this foundation, we provide guidance for expanding to a full microservices ecosystem.
By completing this example, you will:
- Deploy multiple containers to Azure Container Apps
- Implement service-to-service communication with internal networking
- Configure environment-based scaling and health checks
- Monitor distributed applications with Application Insights
- Understand microservices deployment patterns and best practices
- Learn progressive expansion from simple to complex architectures
graph TD
Internet[Internet] -- HTTPS --> Gateway[API Gateway<br/>Node.js Container<br/>Routes requests<br/>Health checks<br/>Request logging]
Gateway -- HTTP internal --> Product[Product Service<br/>Python Container<br/>Product CRUD<br/>In-memory data store<br/>REST API]
Product --> Insights[Application Insights<br/>Monitoring & Logs]
Why Start Simple?
- ✅ Deploy and understand quickly (25-35 minutes)
- ✅ Learn core microservices patterns without complexity
- ✅ Working code you can modify and experiment with
- ✅ Lower cost for learning (~$50-100/month vs $300-1400/month)
- ✅ Build confidence before adding databases and message queues
Analogy: Think of this like learning to drive. You start with an empty parking lot (2 services), master the basics, then progress to city traffic (5+ services with databases).
Once you master the 2-service architecture, you can expand to:
Full Architecture (Not Included - For Reference)
├── API Gateway (✅ Included)
├── Product Service (✅ Included)
├── Order Service (🔜 Add next)
├── User Service (🔜 Add next)
├── Notification Service (🔜 Add last)
├── Azure Service Bus (🔜 For async communication)
├── Cosmos DB (🔜 For product persistence)
├── Azure SQL (🔜 For order management)
└── Azure Storage (🔜 For file storage)
See "Expansion Guide" section at the end for step-by-step instructions.
✅ Service Discovery: Automatic DNS-based discovery between containers
✅ Load Balancing: Built-in load balancing across replicas
✅ Auto-scaling: Independent scaling per service based on HTTP requests
✅ Health Monitoring: Liveness and readiness probes for both services
✅ Distributed Logging: Centralized logging with Application Insights
✅ Internal Networking: Secure service-to-service communication
✅ Container Orchestration: Automatic deployment and scaling
✅ Zero-Downtime Updates: Rolling updates with revision management
Before starting, verify you have these tools installed:
-
Azure Developer CLI (azd) (version 1.0.0 or higher)
azd version # Expected output: azd version 1.0.0 or higher -
Azure CLI (version 2.50.0 or higher)
az --version # Expected output: azure-cli 2.50.0 or higher -
Docker (for local development/testing - optional)
docker --version # Expected output: Docker version 20.10 or higher
- An active Azure subscription (create a free account)
- Permissions to create resources in your subscription
- Contributor role on the subscription or resource group
This is an advanced-level example. You should have:
- Completed the Simple Flask API example
- Basic understanding of microservices architecture
- Familiarity with REST APIs and HTTP
- Understanding of container concepts
New to Container Apps? Start with the Simple Flask API example first to learn the basics.
git clone https://github.com/microsoft/AZD-for-beginners.git
cd AZD-for-beginners/examples/container-app/microservices✓ Success Check: Verify you see azure.yaml:
ls
# Expected: README.md, azure.yaml, infra/, src/azd auth loginThis opens your browser for Azure authentication. Sign in with your Azure credentials.
✓ Success Check: You should see:
Logged in to Azure.
azd initPrompts you'll see:
- Environment name: Enter a short name (e.g.,
microservices-dev) - Azure subscription: Select your subscription
- Azure location: Choose a region (e.g.,
eastus,westeurope)
✓ Success Check: You should see:
SUCCESS: New project initialized!
azd upWhat happens (takes 8-12 minutes):
- Creates Container Apps environment
- Creates Application Insights for monitoring
- Builds API Gateway container (Node.js)
- Builds Product Service container (Python)
- Deploys both containers to Azure
- Configures networking and health checks
- Sets up monitoring and logging
✓ Success Check: You should see:
SUCCESS: Your application was deployed to Azure in X minutes Y seconds.
Endpoint: https://api-gateway-<unique-id>.azurecontainerapps.io
⏱️ Time: 8-12 minutes
# Get the gateway endpoint
GATEWAY_URL=$(azd env get-values | grep API_GATEWAY_URL | cut -d '=' -f2 | tr -d '"')
# Test API Gateway health
curl $GATEWAY_URL/health
# Expected output:
# {"status":"healthy","service":"api-gateway","timestamp":"2025-11-19T10:30:00Z"}Test product service through gateway:
# List products
curl $GATEWAY_URL/api/products
# Expected output:
# [
# {"id":1,"name":"Laptop","price":999.99,"stock":50},
# {"id":2,"name":"Mouse","price":29.99,"stock":200},
# {"id":3,"name":"Keyboard","price":79.99,"stock":150}
# ]✓ Success Check: Both endpoints return JSON data without errors.
🎉 Congratulations! You've deployed a microservices architecture to Azure!
All implementation files are included—this is a complete, working example:
microservices/
│
├── README.md # This file
├── azure.yaml # AZD configuration
├── .gitignore # Git ignore patterns
│
├── infra/ # Infrastructure as Code (Bicep)
│ ├── main.bicep # Main orchestration
│ ├── abbreviations.json # Naming conventions
│ ├── core/ # Shared infrastructure
│ │ ├── container-apps-environment.bicep # Container environment + registry
│ │ └── monitor.bicep # Application Insights + Log Analytics
│ └── app/ # Service definitions
│ ├── api-gateway.bicep # API Gateway container app
│ └── product-service.bicep # Product Service container app
│
└── src/ # Application source code
├── api-gateway/ # Node.js API Gateway
│ ├── app.js # Express server with routing
│ ├── package.json # Node dependencies
│ └── Dockerfile # Container definition
└── product-service/ # Python Product Service
├── main.py # Flask API with product data
├── requirements.txt # Python dependencies
└── Dockerfile # Container definition
What Each Component Does:
Infrastructure (infra/):
main.bicep: Orchestrates all Azure resources and their dependenciescore/container-apps-environment.bicep: Creates the Container Apps environment and Azure Container Registrycore/monitor.bicep: Sets up Application Insights for distributed loggingapp/*.bicep: Individual container app definitions with scaling and health checks
API Gateway (src/api-gateway/):
- Public-facing service that routes requests to backend services
- Implements logging, error handling, and request forwarding
- Demonstrates service-to-service HTTP communication
Product Service (src/product-service/):
- Internal service with product catalog (in-memory for simplicity)
- REST API with health checks
- Example of backend microservice pattern
Port: 8080
Access: Public (external ingress)
Purpose: Routes incoming requests to appropriate backend services
Endpoints:
GET /- Service informationGET /health- Health check endpointGET /api/products- Forward to product service (list all)GET /api/products/:id- Forward to product service (get by ID)
Key Features:
- Request routing with axios
- Centralized logging
- Error handling and timeout management
- Service discovery via environment variables
- Application Insights integration
Code Highlight (src/api-gateway/app.js):
// Internal service communication
app.get('/api/products', async (req, res) => {
const response = await axios.get(`${PRODUCT_SERVICE_URL}/products`);
res.json(response.data);
});Port: 8000
Access: Internal only (no external ingress)
Purpose: Manages product catalog with in-memory data
Endpoints:
GET /- Service informationGET /health- Health check endpointGET /products- List all productsGET /products/<id>- Get product by ID
Key Features:
- RESTful API with Flask
- In-memory product store (simple, no database needed)
- Health monitoring with probes
- Structured logging
- Application Insights integration
Data Model:
{
"id": 1,
"name": "Laptop",
"description": "High-performance laptop",
"price": 999.99,
"stock": 50
}Why Internal Only? The product service is not exposed publicly. All requests must go through the API Gateway, which provides:
- Security: Controlled access point
- Flexibility: Can change backend without affecting clients
- Monitoring: Centralized request logging
In this example, the API Gateway communicates with the Product Service using internal HTTP calls:
// API Gateway (src/api-gateway/app.js)
const PRODUCT_SERVICE_URL = process.env.PRODUCT_SERVICE_URL;
// Make internal HTTP request
const response = await axios.get(`${PRODUCT_SERVICE_URL}/products`);Key Points:
-
DNS-Based Discovery: Container Apps automatically provides DNS for internal services
- Product Service FQDN:
product-service.internal.<environment>.azurecontainerapps.io - Simplified as:
http://product-service(Container Apps resolves it)
- Product Service FQDN:
-
No Public Exposure: Product Service has
external: falsein Bicep- Only accessible within the Container Apps environment
- Cannot be reached from the internet
-
Environment Variables: Service URLs are injected at deployment time
- Bicep passes the internal FQDN to the gateway
- No hardcoded URLs in application code
Analogy: Think of this like office rooms. The API Gateway is the reception desk (public-facing), and the Product Service is an office room (internal only). Visitors must go through reception to reach any office.
# Deploy infrastructure and both services
azd upThis deploys:
- Container Apps environment
- Application Insights
- Container Registry
- API Gateway container
- Product Service container
Time: 8-12 minutes
# Deploy only one service (after initial azd up)
azd deploy api-gateway
# Or deploy product service
azd deploy product-serviceUse Case: When you've updated code in one service and want to redeploy only that service.
# Change scaling parameters
azd env set GATEWAY_MAX_REPLICAS 30
# Redeploy with new configuration
azd upBoth services are configured with HTTP-based autoscaling in their Bicep files:
API Gateway:
- Min replicas: 2 (always at least 2 for availability)
- Max replicas: 20
- Scale trigger: 50 concurrent requests per replica
Product Service:
- Min replicas: 1 (can scale to zero if needed)
- Max replicas: 10
- Scale trigger: 100 concurrent requests per replica
Customize Scaling (in infra/app/*.bicep):
scale: {
minReplicas: 1
maxReplicas: 10
rules: [
{
name: 'http-scale-rule'
http: {
metadata: {
concurrentRequests: '100' // Adjust this
}
}
}
]
}API Gateway:
- CPU: 1.0 vCPU
- Memory: 2 GiB
- Reason: Handles all external traffic
Product Service:
- CPU: 0.5 vCPU
- Memory: 1 GiB
- Reason: Lightweight in-memory operations
Both services include liveness and readiness probes:
probes: [
{
type: 'Liveness'
httpGet: {
path: '/health'
port: 8080
}
initialDelaySeconds: 10
periodSeconds: 30
}
{
type: 'Readiness'
httpGet: {
path: '/health'
port: 8080
}
initialDelaySeconds: 5
periodSeconds: 10
}
]What This Means:
- Liveness: If health check fails, Container Apps restarts the container
- Readiness: If not ready, Container Apps stops routing traffic to that replica
# View logs using azd monitor
azd monitor --logs
# Or use Azure CLI for specific Container Apps:
# Stream logs from API Gateway
az containerapp logs show --name api-gateway --resource-group $RG_NAME --follow
# View recent product service logs
az containerapp logs show --name product-service --resource-group $RG_NAME --tail 100Expected Output:
[api-gateway] API Gateway listening on port 8080
[api-gateway] Product Service URL: http://product-service
[api-gateway] GET /api/products 200 - 45ms
[product-service] Retrieved 5 products
Access Application Insights in Azure Portal, then run these queries:
Find Slow Requests:
requests
| where timestamp > ago(1h)
| where duration > 1000 // Requests taking >1 second
| summarize count() by name, cloud_RoleName
| order by count_ descTrack Service-to-Service Calls:
dependencies
| where timestamp > ago(1h)
| where type == "Http"
| project timestamp, name, target, duration, success
| order by timestamp descError Rate by Service:
exceptions
| where timestamp > ago(24h)
| summarize errorCount = count() by cloud_RoleName, type
| order by errorCount descRequest Volume Over Time:
requests
| where timestamp > ago(1h)
| summarize requestCount = count() by bin(timestamp, 5m), cloud_RoleName
| render timechart# Get Application Insights details
azd env get-values | grep APPLICATIONINSIGHTS
# Open Azure Portal monitoring
az monitor app-insights component show \
--app $(azd env get-values | grep APPLICATIONINSIGHTS_CONNECTION_STRING | cut -d '=' -f2) \
--resource-group $(azd env get-values | grep AZURE_RESOURCE_GROUP | cut -d '=' -f2) \
--query "appId" -o tsv- Navigate to Application Insights in Azure Portal
- Click "Live Metrics"
- See real-time requests, failures, and performance
- Test by running:
curl $(azd env get-values | grep API_GATEWAY_URL | cut -d '=' -f2 | tr -d '"')/api/products
[Note: See full exercises above in the "Practical Exercises" section for detailed step-by-step exercises including deployment verification, data modification, autoscaling tests, error handling, and adding a third service.]
| Resource | Configuration | Estimated Cost |
|---|---|---|
| API Gateway | 2-20 replicas, 1 vCPU, 2GB RAM | $30-150 |
| Product Service | 1-10 replicas, 0.5 vCPU, 1GB RAM | $15-75 |
| Container Registry | Basic tier | $5 |
| Application Insights | 1-2 GB/month | $5-10 |
| Log Analytics | 1 GB/month | $3 |
| Total | $58-243/month |
Cost Breakdown by Usage:
- Light traffic (testing/learning): ~$60/month
- Moderate traffic (small production): ~$120/month
- High traffic (busy periods): ~$240/month
-
Scale to Zero for Development:
scale: { minReplicas: 0 // Save $30-40/month when not in use maxReplicas: 10 }
-
Use Consumption Plan for Cosmos DB (when you add it):
- Pay only for what you use
- No minimum charge
-
Set Application Insights Sampling:
appInsights.defaultClient.config.samplingPercentage = 50; // Sample 50% of requests
-
Clean Up When Not Needed:
azd down
For learning/testing, consider:
- Use Azure free credits (first 30 days)
- Keep to minimum replicas
- Delete after testing (no ongoing charges)
To avoid ongoing charges, delete all resources:
azd down --force --purgeConfirmation Prompt:
? Total resources to delete: 6, are you sure you want to continue? (y/N)
Type y to confirm.
What Gets Deleted:
- Container Apps Environment
- Both Container Apps (gateway & product service)
- Container Registry
- Application Insights
- Log Analytics Workspace
- Resource Group
✓ Verify Cleanup:
az group list --query "[?starts_with(name,'rg-microservices')]" --output tableShould return empty.
Once you've mastered this 2-service architecture, here's how to expand:
Add Cosmos DB for Product Service:
-
Create
infra/core/cosmos.bicep:resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = { name: name location: location kind: 'GlobalDocumentDB' properties: { databaseAccountOfferType: 'Standard' locations: [{ locationName: location, failoverPriority: 0 }] } }
-
Update product service to use Cosmos DB instead of in-memory data
-
Estimated additional cost: ~$25/month (serverless)
Create Order Service:
- New folder:
src/order-service/(Python/Node.js/C#) - New Bicep:
infra/app/order-service.bicep - Update API Gateway to route
/api/orders - Add Azure SQL Database for order persistence
Architecture becomes:
API Gateway → Product Service (Cosmos DB)
→ Order Service (Azure SQL)
Implement Event-Driven Architecture:
- Add Azure Service Bus:
infra/core/servicebus.bicep - Product Service publishes "ProductCreated" events
- Order Service subscribes to product events
- Add Notification Service to process events
Pattern: Request/Response (HTTP) + Event-Driven (Service Bus)
Implement User Service:
- Create
src/user-service/(Go/Node.js) - Add Azure AD B2C or custom JWT authentication
- API Gateway validates tokens
- Services check user permissions
Add These Components:
- Azure Front Door (global load balancing)
- Azure Key Vault (secret management)
- Azure Monitor Workbooks (custom dashboards)
- CI/CD Pipeline (GitHub Actions)
- Blue-Green Deployments
- Managed Identity for all services
Full Production Architecture Cost: ~$300-1,400/month
- Azure Container Apps Documentation
- Microservices Architecture Guide
- Application Insights for Distributed Tracing
- Azure Developer CLI Documentation
- ← Previous: Simple Flask API - Beginner single-container example
- → Next: AI Integration Guide - Add AI capabilities
- 🏠 Course Home
Single Container App (Simple Flask API example):
- ✅ Simple applications
- ✅ Monolithic architecture
- ✅ Fast to deploy
- ❌ Limited scalability
- Cost: ~$15-50/month
Microservices (This example):
- ✅ Complex applications
- ✅ Independent scaling per service
- ✅ Team autonomy (different services, different teams)
- ❌ More complex to manage
- Cost: ~$60-250/month
Kubernetes (AKS):
- ✅ Maximum control and flexibility
- ✅ Multi-cloud portability
- ✅ Advanced networking
- ❌ Requires Kubernetes expertise
- Cost: ~$150-500/month minimum
Recommendation: Start with Container Apps (this example), move to AKS only if you need Kubernetes-specific features.
Q: Why only 2 services instead of 5+?
A: Educational progression. Master the fundamentals (service communication, monitoring, scaling) with a simple example before adding complexity. The patterns you learn here apply to 100-service architectures.
Q: Can I add more services myself?
A: Absolutely! Follow the expansion guide above. Each new service follows the same pattern: create src folder, create Bicep file, update azure.yaml, deploy.
Q: Is this production-ready?
A: It's a solid foundation. For production, add: managed identity, Key Vault, persistent databases, CI/CD pipeline, monitoring alerts, and backup strategy.
Q: Why not use Dapr or other service mesh?
A: Keep it simple for learning. Once you understand native Container Apps networking, you can layer on Dapr for advanced scenarios.
Q: How do I debug locally?
A: Run services locally with Docker:
cd src/api-gateway
docker build -t local-gateway .
docker run -p 8080:8080 -e PRODUCT_SERVICE_URL=http://localhost:8000 local-gatewayQ: Can I use different programming languages?
A: Yes! This example shows Node.js (gateway) + Python (product service). You can mix any languages that run in containers.
Q: What if I don't have Azure credits?
A: Use Azure free tier (first 30 days with new accounts) or deploy for short testing periods and delete immediately.
🎓 Learning Path Summary: You've learned to deploy a multi-service architecture with automatic scaling, internal networking, centralized monitoring, and production-ready patterns. This foundation prepares you for complex distributed systems and enterprise microservices architectures.
📚 Course Navigation:
- ← Previous: Simple Flask API
- → Next: Database Integration Example
- 🏠 Course Home
- 📖 Container Apps Best Practices