Chapter Navigation:
- 📚 Course Home: AZD For Beginners
- 📖 Current Chapter: Chapter 4 - Infrastructure as Code & Deployment
- ⬅️ Previous Chapter: Chapter 3: Configuration
- ➡️ Next: Provisioning Resources
- 🚀 Next Chapter: Chapter 5: Multi-Agent AI Solutions
This comprehensive guide covers everything you need to know about deploying applications using Azure Developer CLI, from basic single-command deployments to advanced production scenarios with custom hooks, multiple environments, and CI/CD integration. Master the complete deployment lifecycle with practical examples and best practices.
By completing this guide, you will:
- Master all Azure Developer CLI deployment commands and workflows
- Understand the complete deployment lifecycle from provisioning to monitoring
- Implement custom deployment hooks for pre and post-deployment automation
- Configure multiple environments with environment-specific parameters
- Set up advanced deployment strategies including blue-green and canary deployments
- Integrate azd deployments with CI/CD pipelines and DevOps workflows
Upon completion, you will be able to:
- Execute and troubleshoot all azd deployment workflows independently
- Design and implement custom deployment automation using hooks
- Configure production-ready deployments with proper security and monitoring
- Manage complex multi-environment deployment scenarios
- Optimize deployment performance and implement rollback strategies
- Integrate azd deployments into enterprise DevOps practices
Azure Developer CLI provides several deployment commands:
azd up- Complete workflow (provision + deploy)azd provision- Create/update Azure resources onlyazd deploy- Deploy application code onlyazd package- Build and package applications
The most common workflow for new projects:
# Deploy everything from scratch
azd up
# Deploy with specific environment
azd up --environment production
# Deploy with custom parameters
azd up --parameter location=westus2 --parameter sku=P1v2When you only need to update Azure resources:
# Provision/update infrastructure
azd provision
# Provision with dry-run to preview changes
azd provision --preview
# Provision specific services
azd provision --service databaseFor quick application updates:
# Deploy all services
azd deploy
# Expected output:
# Deploying services (azd deploy)
# - web: Deploying... Done
# - api: Deploying... Done
# SUCCESS: Your deployment completed in 2 minutes 15 seconds
# Deploy specific service
azd deploy --service web
azd deploy --service api
# Deploy with custom build arguments
azd deploy --service api --build-arg NODE_ENV=production
# Verify deployment
azd show --output json | jq '.services'After any deployment, verify success:
# Check all services are running
azd show
# Test health endpoints
WEB_URL=$(azd show --output json | jq -r '.services.web.endpoint')
API_URL=$(azd show --output json | jq -r '.services.api.endpoint')
curl -f "$WEB_URL/health" || echo "❌ Web health check failed"
curl -f "$API_URL/health" || echo "❌ API health check failed"
# Monitor for errors (opens in browser by default)
azd monitor --logsSuccess Criteria:
- ✅ All services show "Running" status
- ✅ Health endpoints return HTTP 200
- ✅ No error logs in the last 5 minutes
- ✅ Application responds to test requests
# azure.yaml
hooks:
preprovision:
shell: sh
run: |
echo "Validating configuration..."
./scripts/validate-prereqs.sh
echo "Setting up secrets..."
./scripts/setup-secrets.sh- Reads infrastructure templates (Bicep/Terraform)
- Creates or updates Azure resources
- Configures networking and security
- Sets up monitoring and logging
hooks:
postprovision:
shell: pwsh
run: |
Write-Host "Infrastructure ready, setting up databases..."
./scripts/setup-database.ps1
Write-Host "Configuring application settings..."
./scripts/configure-app-settings.ps1- Builds application code
- Creates deployment artifacts
- Packages for target platform (containers, ZIP files, etc.)
hooks:
predeploy:
shell: sh
run: |
echo "Running pre-deployment tests..."
npm run test:unit
echo "Database migrations..."
npm run db:migrate- Deploys packaged applications to Azure services
- Updates configuration settings
- Starts/restarts services
hooks:
postdeploy:
shell: sh
run: |
echo "Running integration tests..."
npm run test:integration
echo "Warming up applications..."
curl https://${WEB_URL}/health# azure.yaml
services:
web:
project: ./src/web
host: staticwebapp
buildCommand: npm run build
outputPath: dist
api:
project: ./src/api
host: containerapp
docker:
context: ./src/api
dockerfile: Dockerfile
target: production
env:
- name: NODE_ENV
value: production
- name: API_VERSION
value: "1.0.0"
worker:
project: ./src/worker
host: function
runtime: node
buildCommand: npm install --production# Development environment
azd env set NODE_ENV development
azd env set DEBUG true
azd env set LOG_LEVEL debug
# Staging environment
azd env new staging
azd env set NODE_ENV staging
azd env set DEBUG false
azd env set LOG_LEVEL info
# Production environment
azd env new production
azd env set NODE_ENV production
azd env set DEBUG false
azd env set LOG_LEVEL error# Complex application with multiple services
services:
# Frontend applications
web-app:
project: ./src/web
host: staticwebapp
admin-portal:
project: ./src/admin
host: appservice
# Backend services
user-api:
project: ./src/services/users
host: containerapp
order-api:
project: ./src/services/orders
host: containerapp
payment-api:
project: ./src/services/payments
host: function
# Background processing
notification-worker:
project: ./src/workers/notifications
host: containerapp
report-worker:
project: ./src/workers/reports
host: function# Create blue environment
azd env new production-blue
azd up --environment production-blue
# Test blue environment
./scripts/test-environment.sh production-blue
# Switch traffic to blue (manual DNS/load balancer update)
./scripts/switch-traffic.sh production-blue
# Clean up green environment
azd env select production-green
azd down --force# azure.yaml - Configure traffic splitting
services:
api:
project: ./src/api
host: containerapp
trafficSplit:
- revision: stable
percentage: 90
- revision: canary
percentage: 10#!/bin/bash
# deploy-staged.sh
echo "Deploying to development..."
azd env select dev
azd up --confirm-with-no-prompt
echo "Running dev tests..."
./scripts/test-environment.sh dev
echo "Deploying to staging..."
azd env select staging
azd up --confirm-with-no-prompt
echo "Running staging tests..."
./scripts/test-environment.sh staging
echo "Manual approval required for production..."
read -p "Deploy to production? (y/N): " confirm
if [[ $confirm == [yY] ]]; then
echo "Deploying to production..."
azd env select production
azd up --confirm-with-no-prompt
echo "Running production smoke tests..."
./scripts/test-environment.sh production
fiservices:
api:
project: ./src/api
host: containerapp
docker:
context: ./src/api
dockerfile: Dockerfile
target: production
buildArgs:
BUILD_VERSION: ${BUILD_VERSION}
NODE_ENV: production
env:
- name: DATABASE_URL
value: ${DATABASE_URL}
secrets:
- name: jwt-secret
value: ${JWT_SECRET}
scale:
minReplicas: 1
maxReplicas: 10# Dockerfile
FROM node:18-alpine AS base
WORKDIR /app
COPY package*.json ./
FROM base AS development
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
FROM base AS build
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["npm", "start"]# Deploy a specific service for faster iteration
azd deploy --service web
azd deploy --service api
# Deploy all services
azd deploy# azure.yaml - Configure build commands
services:
web:
project: ./src/web
buildCommand: npm run build
outputPath: dist# Use azd deploy (not azd up) for code-only changes
# This skips infrastructure provisioning and is much faster
azd deploy
# Deploy specific service for fastest iteration
azd deploy --service api# Monitor application in real-time
azd monitor --live
# View application logs
azd monitor --logs
# Check deployment status
azd show# azure.yaml - Configure health checks
services:
api:
project: ./src/api
host: containerapp
healthCheck:
path: /health
interval: 30s
timeout: 10s
retries: 3#!/bin/bash
# scripts/validate-deployment.sh
echo "Validating deployment..."
# Check application health
WEB_URL=$(azd show --output json | jq -r '.services.web.endpoint')
API_URL=$(azd show --output json | jq -r '.services.api.endpoint')
echo "Testing web application..."
if curl -f "$WEB_URL/health"; then
echo "✅ Web application is healthy"
else
echo "❌ Web application health check failed"
exit 1
fi
echo "Testing API..."
if curl -f "$API_URL/health"; then
echo "✅ API is healthy"
else
echo "❌ API health check failed"
exit 1
fi
echo "Running integration tests..."
npm run test:integration
echo "✅ Deployment validation completed successfully"# Store secrets securely
azd env set DATABASE_PASSWORD "$(openssl rand -base64 32)" --secret
azd env set JWT_SECRET "$(openssl rand -base64 64)" --secret
azd env set API_KEY "your-api-key" --secret
# Reference secrets in azure.yamlservices:
api:
secrets:
- name: database-password
value: ${DATABASE_PASSWORD}
- name: jwt-secret
value: ${JWT_SECRET}# azure.yaml - Configure network security
infra:
parameters:
enablePrivateEndpoints: true
allowedIPs:
- "203.0.113.0/24" # Office IP range
- "198.51.100.0/24" # VPN IP rangeservices:
api:
project: ./src/api
host: containerapp
identity:
type: systemAssigned
keyVault:
- name: app-secrets
secrets:
- database-connection
- external-api-key# AZD doesn't have built-in rollback. Recommended approaches:
# Option 1: Redeploy from Git (recommended)
git revert HEAD # Revert the problematic commit
git push
azd deploy
# Option 2: Redeploy specific commit
git checkout <previous-commit-hash>
azd deploy
git checkout main# Preview infrastructure changes before applying
azd provision --preview
# For infrastructure rollback, use version control:
git revert HEAD # Revert infrastructure changes
azd provision # Apply previous infrastructure state#!/bin/bash
# scripts/rollback-database.sh
echo "Rolling back database migrations..."
npm run db:rollback
echo "Validating database state..."
npm run db:validate
echo "Database rollback completed"# View current deployment status
azd show
# Monitor application with Application Insights
azd monitor --overview
# View live metrics
azd monitor --live# azure.yaml - Configure custom metrics
hooks:
postdeploy:
shell: sh
run: |
# Record deployment metrics
DEPLOY_TIME=$(date +%s)
SERVICE_COUNT=$(azd show --output json | jq '.services | length')
# Send to monitoring system
curl -X POST "https://metrics.company.com/deployments" \
-H "Content-Type: application/json" \
-d "{\"timestamp\": $DEPLOY_TIME, \"service_count\": $SERVICE_COUNT}"# Use consistent naming
azd env new dev-$(whoami)
azd env new staging-$(git rev-parse --short HEAD)
azd env new production-v1
# Maintain environment parity
./scripts/sync-environments.sh# Preview infrastructure changes before deployment
azd provision --preview
# Use ARM/Bicep linting
az bicep lint --file infra/main.bicep
# Validate Bicep syntax
az bicep build --file infra/main.bicephooks:
predeploy:
shell: sh
run: |
# Unit tests
npm run test:unit
# Security scanning
npm audit
# Code quality checks
npm run lint
npm run type-check
postdeploy:
shell: sh
run: |
# Integration tests
npm run test:integration
# Performance tests
npm run test:performance
# Smoke tests
npm run test:smoke# Document deployment procedures
echo "# Deployment Log - $(date)" >> DEPLOYMENT.md
echo "Environment: $(azd env get-value AZURE_ENV_NAME)" >> DEPLOYMENT.md
echo "Services deployed: $(azd show --output json | jq -r '.services | keys | join(", ")')" >> DEPLOYMENT.md- Provisioning Resources - Deep dive into infrastructure management
- Pre-Deployment Planning - Plan your deployment strategy
- Common Issues - Resolve deployment issues
- Best Practices - Production-ready deployment strategies
Goal: Master the difference between full and incremental deployments
# Initial deployment
mkdir deployment-practice && cd deployment-practice
azd init --template todo-nodejs-mongo
azd up
# Record initial deployment time
echo "Full deployment: $(date)" > deployment-log.txt
# Make a code change
echo "// Updated $(date)" >> src/api/src/server.js
# Deploy only code (fast)
time azd deploy
echo "Code-only deployment: $(date)" >> deployment-log.txt
# Compare times
cat deployment-log.txt
# Clean up
azd down --force --purgeSuccess Criteria:
- Full deployment takes 5-15 minutes
- Code-only deployment takes 2-5 minutes
- Code changes reflected in deployed app
- Infrastructure unchanged after
azd deploy
Learning Outcome: azd deploy is 50-70% faster than azd up for code changes
Goal: Implement pre and post-deployment automation
# Create pre-deploy validation script
mkdir -p scripts
cat > scripts/pre-deploy-check.sh << 'EOF'
#!/bin/bash
echo "⚠️ Running pre-deployment checks..."
# Check if tests pass
if ! npm run test:unit; then
echo "❌ Tests failed! Aborting deployment."
exit 1
fi
# Check for uncommitted changes
if [[ -n $(git status -s) ]]; then
echo "⚠️ Warning: Uncommitted changes detected"
fi
echo "✅ Pre-deployment checks passed!"
EOF
chmod +x scripts/pre-deploy-check.sh
# Create post-deploy smoke test
cat > scripts/post-deploy-test.sh << 'EOF'
#!/bin/bash
echo "💨 Running smoke tests..."
WEB_URL=$(azd show --output json | jq -r '.services.web.endpoint')
if curl -f "$WEB_URL/health"; then
echo "✅ Health check passed!"
else
echo "❌ Health check failed!"
exit 1
fi
echo "✅ Smoke tests completed!"
EOF
chmod +x scripts/post-deploy-test.sh
# Add hooks to azure.yaml
cat >> azure.yaml << 'EOF'
hooks:
predeploy:
shell: sh
run: ./scripts/pre-deploy-check.sh
postdeploy:
shell: sh
run: ./scripts/post-deploy-test.sh
EOF
# Test deployment with hooks
azd deploySuccess Criteria:
- Pre-deploy script runs before deployment
- Deployment aborts if tests fail
- Post-deploy smoke test validates health
- Hooks execute in correct order
Goal: Implement staged deployment workflow (dev → staging → production)
# Create deployment script
cat > deploy-staged.sh << 'EOF'
#!/bin/bash
set -e
echo "🚀 Staged Deployment Workflow"
echo "=============================="
# Step 1: Deploy to dev
echo "
🛠️ Step 1: Deploying to development..."
azd env select dev
azd up --no-prompt
echo "Running dev tests..."
curl -f $(azd show --output json | jq -r '.services.web.endpoint')/health
# Step 2: Deploy to staging
echo "
🔍 Step 2: Deploying to staging..."
azd env select staging
azd up --no-prompt
echo "Running staging tests..."
curl -f $(azd show --output json | jq -r '.services.web.endpoint')/health
# Step 3: Manual approval for production
echo "
✅ Dev and staging deployments successful!"
read -p "Deploy to production? (yes/no): " confirm
if [[ $confirm == "yes" ]]; then
echo "
🎉 Step 3: Deploying to production..."
azd env select production
azd up --no-prompt
echo "Running production smoke tests..."
curl -f $(azd show --output json | jq -r '.services.web.endpoint')/health
echo "
✅ Production deployment completed!"
else
echo "❌ Production deployment cancelled"
fi
EOF
chmod +x deploy-staged.sh
# Create environments
azd env new dev
azd env new staging
azd env new production
# Run staged deployment
./deploy-staged.shSuccess Criteria:
- Dev environment deploys successfully
- Staging environment deploys successfully
- Manual approval required for production
- All environments have working health checks
- Can roll back if needed
Goal: Implement and test deployment rollback using Git
# Deploy v1
azd env set APP_VERSION "1.0.0"
azd up
# Save v1 commit hash
V1_COMMIT=$(git rev-parse HEAD)
echo "v1 commit: $V1_COMMIT"
# Deploy v2 with breaking change
echo "throw new Error('Intentional break')" >> src/api/src/server.js
git add . && git commit -m "v2 with intentional break"
azd env set APP_VERSION "2.0.0"
azd deploy
# Detect failure and rollback
if ! curl -f $(azd show --output json | jq -r '.services.api.endpoint')/health; then
echo "❌ v2 deployment failed! Rolling back..."
# Rollback using git
git revert HEAD --no-edit
# Rollback environment
azd env set APP_VERSION "1.0.0"
# Redeploy v1
azd deploy
echo "✅ Rolled back to v1.0.0"
fiSuccess Criteria:
- Can detect deployment failures
- Rollback script executes automatically
- Application returns to working state
- Health checks pass after rollback
# Create deployment metrics script
cat > track-deployment.sh << 'EOF'
#!/bin/bash
START_TIME=$(date +%s)
azd deploy "$@"
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo "
📊 Deployment Metrics:"
echo "Duration: ${DURATION}s"
echo "Timestamp: $(date)"
echo "Environment: $(azd env get-value AZURE_ENV_NAME)"
echo "Services: $(azd show --output json | jq -r '.services | keys | join(", ")')"
# Log to file
echo "$(date +%Y-%m-%d,%H:%M:%S),$DURATION,$(azd env get-value AZURE_ENV_NAME)" >> deployment-metrics.csv
EOF
chmod +x track-deployment.sh
# Use it
./track-deployment.shAnalyze your metrics:
# View deployment history
cat deployment-metrics.csv
# Calculate average deployment time
awk -F',' '{sum+=$2; count++} END {print "Average: " sum/count "s"}' deployment-metrics.csv- Azure Developer CLI Deployment Reference
- Azure App Service Deployment
- Azure Container Apps Deployment
- Azure Functions Deployment
Navigation
- Previous Lesson: Your First Project
- Next Lesson: Provisioning Resources