· Lincoln J Bicalho  · 24 min read

Deploying Your Vanna AI + Blazor Application: A Production-Ready Guide

Learn how to deploy a production-ready Vanna AI + Blazor application to Azure with proper security, monitoring, and performance optimization. Complete deployment strategies with real configurations and troubleshooting guidance.

Learn how to deploy a production-ready Vanna AI + Blazor application to Azure with proper security, monitoring, and performance optimization. Complete deployment strategies with real configurations and troubleshooting guidance.

📋 Prerequisites:

  • .NET 8 SDK or later installed
  • Azure subscription with appropriate permissions
  • Completed Vanna AI + Blazor application from Parts 1-2
  • Azure CLI installed and configured
  • Basic understanding of Azure App Service and SQL Database
  • Vanna AI API key and trained model

Overview

Deploying a Vanna AI + Blazor application requires coordinating multiple Azure services while maintaining security, performance, and reliability. This guide walks you through production deployment strategies that ensure your AI-powered application runs efficiently and securely at scale.

What you’ll learn:

  • How to choose the right deployment strategy for your requirements
  • Step-by-step Azure infrastructure configuration
  • Security best practices for AI-enabled applications
  • Monitoring and troubleshooting production deployments
  • Performance optimization for real-time AI queries

Why deployment complexity matters:

Your Vanna AI + Blazor application has unique deployment requirements that go beyond standard web applications. The combination of SignalR for real-time communication, external AI API dependencies, and database connectivity creates specific challenges that require careful planning.

Understanding Deployment Requirements

Before you begin deployment, you need to understand what makes this application different from standard Blazor deployments.

Core Components

Your application architecture includes:

1. Blazor Server Application

  • Requires persistent SignalR connections
  • Needs sticky sessions for load balancing
  • Demands Always On configuration to maintain circuits

2. Vanna AI Integration

  • Requires outbound HTTPS access to Vanna API endpoints
  • Needs secure API key storage and rotation
  • May have variable response times requiring timeout configuration

3. Database Connections

  • Application database for user data and query history
  • Target database(s) that Vanna queries against
  • Connection pooling and retry logic requirements

4. Real-time Features

  • SignalR for live query results and progress updates
  • WebSocket support across the infrastructure
  • State management across server reconnections

⚠️ Warning: Blazor Server applications require WebSocket support and sticky sessions when deployed behind load balancers. Failure to configure these correctly will result in frequent disconnections and poor user experience.

Deployment Strategy Comparison

Choose your deployment approach based on your requirements, budget, and scale needs.

StrategyComplexityCostScaleBest For
Single App ServiceLow$Small-MediumDevelopment, small teams, proof of concepts
App Service + Staging SlotMedium$$MediumProduction with testing requirements
Container-based (Docker)High$$$LargeMulti-environment, microservices architecture
App Service EnvironmentVery High$$$$EnterpriseCompliance requirements, network isolation

Strategy 1: Single App Service (Basic Production)

When to use:

  • Small to medium user base (under 10,000 daily users)
  • Limited budget constraints
  • Straightforward deployment needs

Trade-offs:

  • ✅ Pros: Simple setup, lower cost, easy to manage
  • ❌ Cons: No staging environment, requires downtime for deployments, limited scaling options

When to use:

  • Production environments requiring zero-downtime deployments
  • Need for testing before production release
  • Medium to large user base

Trade-offs:

  • ✅ Pros: Zero-downtime swaps, production testing, easy rollback
  • ❌ Cons: Higher cost, more complex configuration

Strategy 3: Container-based Deployment

When to use:

  • Multi-environment consistency requirements
  • Advanced DevOps pipelines
  • Microservices architecture

Trade-offs:

  • ✅ Pros: Environment consistency, advanced orchestration, flexible scaling
  • ❌ Cons: Steep learning curve, infrastructure complexity, higher operational overhead

💡 Tip: For government systems and enterprise deployments, we recommend Strategy 2 (deployment slots) as it provides the best balance of simplicity, reliability, and cost-effectiveness. This approach has proven reliable in our production environments serving large user bases.

Step 1: Preparing Your Application for Deployment

Configure your application for production deployment by setting up environment-specific configurations and health monitoring.

Configure Production Settings

Create environment-specific configuration that separates development and production settings.

FILE: appsettings.Production.json

{
  "ConnectionStrings": {
    // WHY: Connection strings are populated from Azure Key Vault
    "DefaultConnection": "",
    "DataWarehouse": ""
  },
  "VannaAI": {
    // WHY: API key stored in Key Vault, referenced here by placeholder
    "ApiKey": "",
    "BaseUrl": "https://api.vanna.ai/rpc",
    "Model": "your-model-name",
    // WHY: Production timeout set higher to handle complex queries
    "TimeoutSeconds": 60,
    // WHY: Circuit breaker prevents cascading failures
    "EnableCircuitBreaker": true,
    "MaxRetryAttempts": 3
  },
  "Logging": {
    "LogLevel": {
      // WHY: Production logging focuses on warnings and errors
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.AspNetCore.SignalR": "Warning",
      "VannaAI": "Information"
    },
    "ApplicationInsights": {
      "LogLevel": {
        "Default": "Information"
      }
    }
  },
  "AllowedHosts": "*",
  // WHY: SignalR configuration for production scale
  "SignalR": {
    "KeepAliveInterval": "00:00:15",
    "HandshakeTimeout": "00:00:15",
    "MaximumReceiveMessageSize": 32768
  }
}

Implement Health Checks

Add comprehensive health checks to monitor all application dependencies.

FILE: Program.cs PURPOSE: Configure health monitoring for production deployments

// WHY: Health checks enable Azure to monitor application health
// HOW: Probes check database connectivity, Vanna API, and custom services
builder.Services.AddHealthChecks()
    // Database connectivity checks
    .AddSqlServer(
        connectionString: builder.Configuration.GetConnectionString("DefaultConnection")!,
        healthQuery: "SELECT 1",
        name: "application-db",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { "db", "sql" })
    .AddSqlServer(
        connectionString: builder.Configuration.GetConnectionString("DataWarehouse")!,
        healthQuery: "SELECT 1",
        name: "warehouse-db",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { "db", "warehouse" })
    // External API health check
    .AddUrlGroup(
        uri: new Uri("https://api.vanna.ai/health"),
        name: "vanna-api",
        failureStatus: HealthStatus.Degraded,
        tags: new[] { "external", "ai" })
    // Custom Vanna service check
    .AddCheck<VannaHealthCheck>(
        name: "vanna-service",
        failureStatus: HealthStatus.Unhealthy,
        tags: new[] { "service", "ai" });

// Map health check endpoints
app.MapHealthChecks("/health", new HealthCheckOptions
{
    // WHY: Return detailed status for monitoring dashboards
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
    // WHY: Filter critical checks for readiness probe
    Predicate = _ => true
});

// Separate readiness probe for load balancer
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("db") || check.Tags.Contains("service")
});

FILE: HealthChecks/VannaHealthCheck.cs PURPOSE: Validate Vanna AI service connectivity

// WHY: Custom health check verifies Vanna AI is responsive
// HOW: Performs lightweight API test without consuming quota
public class VannaHealthCheck : IHealthCheck
{
    private readonly IVannaService _vannaService;
    private readonly ILogger<VannaHealthCheck> _logger;

    public VannaHealthCheck(
        IVannaService vannaService,
        ILogger<VannaHealthCheck> logger)
    {
        _vannaService = vannaService;
        _logger = logger;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // WHY: Validate connection without expensive query
            // HOW: Simple API ping or cached validation result
            var isHealthy = await _vannaService.ValidateConnectionAsync(cancellationToken);

            if (isHealthy)
            {
                return HealthCheckResult.Healthy(
                    "Vanna AI service is responsive and configured correctly");
            }

            return HealthCheckResult.Degraded(
                "Vanna AI service responded but may have configuration issues");
        }
        catch (HttpRequestException ex)
        {
            _logger.LogError(ex, "Vanna AI health check failed due to network error");
            return HealthCheckResult.Unhealthy(
                $"Vanna AI service unreachable: {ex.Message}");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Vanna AI health check failed");
            return HealthCheckResult.Unhealthy(
                $"Vanna AI service check failed: {ex.Message}");
        }
    }
}

Configure Production Logging

Set up structured logging that integrates with Azure monitoring services.

FILE: Program.cs PURPOSE: Configure Azure-integrated logging

// WHY: Production logging requires different configuration than development
// HOW: Azure App Service diagnostics integration
if (builder.Environment.IsProduction())
{
    // Enable Azure App Service logging
    builder.Logging.AddAzureWebAppDiagnostics();

    // Configure file-based logging
    builder.Services.Configure<AzureFileLoggerOptions>(options =>
    {
        // WHY: Structured filename for log rotation
        options.FileName = "vanna-blazor-";
        // WHY: 50MB limit prevents disk space issues
        options.FileSizeLimit = 50 * 1024 * 1024;
        // WHY: Keep 5 files for troubleshooting history
        options.RetainedFileCountLimit = 5;
    });

    // Configure blob storage logging for long-term retention
    builder.Services.Configure<AzureBlobLoggerOptions>(options =>
    {
        options.BlobName = "vanna-app-logs.txt";
    });
}

💡 Tip: Configure different log retention policies for file-based (short-term debugging) and blob-based (compliance and long-term analysis) logging. In our deployments, we keep 5 days of file logs and 90 days of blob logs.

Step 2: Creating Azure Infrastructure

Set up the required Azure resources using Infrastructure as Code principles.

Create Resource Group and App Service Plan

Organize resources and choose the appropriate service tier for your workload.

# WHY: Login establishes authenticated session for resource creation
az login

# WHY: Resource group provides logical organization and lifecycle management
# HOW: East US chosen for lower latency to target user base
az group create \
  --name rg-vanna-blazor-prod \
  --location eastus

# WHY: S1 tier required for Always On and custom domains
# HOW: Standard tier provides production features without premium cost
az appservice plan create \
  --name plan-vanna-blazor \
  --resource-group rg-vanna-blazor-prod \
  --sku S1 \
  --is-linux false \
  --location eastus

ℹ️ Note: The S1 (Standard) tier is the minimum recommended for production Blazor Server applications. It provides Always On, automatic scaling, and custom SSL certificates. Basic tier lacks Always On, which causes circuit disconnections.

Create Azure SQL Database

Set up the application database with appropriate security and performance settings.

# WHY: SQL Server provides managed database engine with built-in security
# HOW: Choose admin credentials that meet Azure security requirements
az sql server create \
  --name sql-vanna-blazor-prod \
  --resource-group rg-vanna-blazor-prod \
  --location eastus \
  --admin-user sqladmin \
  --admin-password 'YourSecurePassword123!'

# WHY: Enable Azure services to connect to database
# HOW: Special firewall rule (0.0.0.0) allows Azure resources
az sql server firewall-rule create \
  --resource-group rg-vanna-blazor-prod \
  --server sql-vanna-blazor-prod \
  --name AllowAzureServices \
  --start-ip-address 0.0.0.0 \
  --end-ip-address 0.0.0.0

# WHY: S0 tier provides good balance of performance and cost
# HOW: Can scale up/down based on usage patterns
az sql db create \
  --resource-group rg-vanna-blazor-prod \
  --server sql-vanna-blazor-prod \
  --name VannaBlazorDB \
  --service-objective S0 \
  --backup-storage-redundancy Local

⚠️ Warning: The 0.0.0.0 firewall rule allows all Azure services to connect. For production environments with stricter security requirements, use Virtual Network integration and private endpoints instead.

Create App Service with Required Configuration

Configure the web app with Blazor Server-specific settings.

# WHY: Create web app with .NET 8 runtime
# HOW: Runtime must match your application's target framework
az webapp create \
  --resource-group rg-vanna-blazor-prod \
  --plan plan-vanna-blazor \
  --name webapp-vanna-blazor-prod \
  --runtime "DOTNET|8.0"

# WHY: Always On prevents app from idling and losing SignalR connections
# HOW: Keeps application warm and maintains circuit state
az webapp config set \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --always-on true \
  --use-32bit-worker-process false

# WHY: WebSockets required for SignalR communication
# HOW: Enables bidirectional communication for real-time features
az webapp config set \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --web-sockets-enabled true

# WHY: Enable detailed error pages during initial deployment
# HOW: Shows specific errors instead of generic 500 pages
az webapp config set \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --http20-enabled true

Step 3: Securing Configuration with Azure Key Vault

Store sensitive configuration in Azure Key Vault for enhanced security and secret management.

Create and Configure Key Vault

# WHY: Key Vault provides secure storage for secrets and keys
# HOW: Separate vault per environment for isolation
az keyvault create \
  --name kv-vanna-blazor-prod \
  --resource-group rg-vanna-blazor-prod \
  --location eastus \
  --enable-rbac-authorization false

# WHY: Store Vanna API key securely
# HOW: Double-dash notation maps to configuration sections
az keyvault secret set \
  --vault-name kv-vanna-blazor-prod \
  --name "VannaAI--ApiKey" \
  --value "your-vanna-api-key-here"

# WHY: Connection strings contain credentials requiring protection
# HOW: Secret name structure matches configuration path
az keyvault secret set \
  --vault-name kv-vanna-blazor-prod \
  --name "ConnectionStrings--DefaultConnection" \
  --value "Server=tcp:sql-vanna-blazor-prod.database.windows.net,1433;Database=VannaBlazorDB;User ID=sqladmin;Password=YourSecurePassword123!;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"

# WHY: Store data warehouse connection separately
az keyvault secret set \
  --vault-name kv-vanna-blazor-prod \
  --name "ConnectionStrings--DataWarehouse" \
  --value "your-warehouse-connection-string"

Configure Managed Identity

Enable the App Service to access Key Vault without storing credentials.

# WHY: Managed identity provides Azure AD authentication without credentials
# HOW: System-assigned identity tied to app lifecycle
az webapp identity assign \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod

# WHY: Retrieve identity for access policy configuration
# HOW: Principal ID uniquely identifies the managed identity
PRINCIPAL_ID=$(az webapp identity show \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --query principalId \
  --output tsv)

# WHY: Grant app permission to read secrets from vault
# HOW: Get and List permissions enable configuration reading
az keyvault set-policy \
  --name kv-vanna-blazor-prod \
  --object-id $PRINCIPAL_ID \
  --secret-permissions get list

Update Application to Use Key Vault

Configure your application to retrieve secrets from Azure Key Vault.

FILE: Program.cs PURPOSE: Integrate Azure Key Vault configuration

// WHY: Load secrets from Key Vault in production environments
// HOW: DefaultAzureCredential uses managed identity automatically
if (builder.Environment.IsProduction())
{
    var keyVaultUrl = builder.Configuration["KeyVaultUrl"];

    if (!string.IsNullOrEmpty(keyVaultUrl))
    {
        // WHY: AddAzureKeyVault loads all secrets matching config structure
        // HOW: Double-dash in secret names maps to configuration sections
        builder.Configuration.AddAzureKeyVault(
            new Uri(keyVaultUrl),
            new DefaultAzureCredential());

        // WHY: Log successful Key Vault connection (without exposing secrets)
        builder.Logging.AddConsole().LogInformation(
            "Connected to Azure Key Vault: {KeyVaultUrl}", keyVaultUrl);
    }
    else
    {
        throw new InvalidOperationException(
            "KeyVaultUrl configuration is required for production deployment");
    }
}

Configure the Key Vault URL in App Service settings:

# WHY: App setting provides Key Vault URL without hardcoding
# HOW: Application reads this at startup to connect to vault
az webapp config appsettings set \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --settings KeyVaultUrl="https://kv-vanna-blazor-prod.vault.azure.net/"

💡 Tip: Use Key Vault references in app settings for sensitive values that change frequently. This allows updating secrets without redeploying the application. In our government systems, this simplified quarterly API key rotation.

Step 4: Deploying the Application

Deploy your application using your preferred method.

Deployment Strategy: Visual Studio Publish Profile

Create a publish profile for streamlined deployments from Visual Studio.

FILE: Properties/PublishProfiles/Azure-Production.pubxml PURPOSE: Visual Studio deployment configuration

<?xml version="1.0" encoding="utf-8"?>
<!--
  WHY: Publish profile enables one-click deployment from Visual Studio
  HOW: MSDeploy protocol securely publishes to Azure App Service
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <WebPublishMethod>MSDeploy</WebPublishMethod>
    <ResourceId>/subscriptions/{your-subscription-id}/resourceGroups/rg-vanna-blazor-prod/providers/Microsoft.Web/sites/webapp-vanna-blazor-prod</ResourceId>
    <PublishProvider>AzureWebSite</PublishProvider>
    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
    <LastUsedPlatform>Any CPU</LastUsedPlatform>
    <SiteUrlToLaunchAfterPublish>https://webapp-vanna-blazor-prod.azurewebsites.net</SiteUrlToLaunchAfterPublish>
    <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
    <SkipExtraFilesOnServer>True</SkipExtraFilesOnServer>
    <MSDeployPublishMethod>WMSVC</MSDeployPublishMethod>
    <PublishUrl>webapp-vanna-blazor-prod.scm.azurewebsites.net:443</PublishUrl>
    <UserName>$webapp-vanna-blazor-prod</UserName>
    <!-- Password retrieved from publish profile download -->
    <_SavePWD>True</_SavePWD>
  </PropertyGroup>
</Project>

Deploy using command-line tools for automation and repeatability.

# STEP 1: Build application in Release configuration
# WHY: Release build includes optimizations and excludes debug symbols
# HOW: Output directory prepared for packaging
dotnet publish \
  --configuration Release \
  --output ./publish \
  --self-contained false

# STEP 2: Create deployment package
# WHY: ZIP deployment is reliable and atomic
# HOW: Package all files maintaining directory structure
cd publish
zip -r ../app.zip .
cd ..

# STEP 3: Deploy to Azure App Service
# WHY: ZIP deployment ensures all files deployed together
# HOW: App Service extracts and runs from deployment
az webapp deploy \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --src-path app.zip \
  --type zip \
  --async false

# STEP 4: Verify deployment
# WHY: Confirms application started successfully
# HOW: Check application logs and health endpoint
az webapp log tail \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod

⚠️ Warning: Always use --async false for production deployments to ensure deployment completes before the command returns. Async deployments may appear successful but fail during extraction or startup.

Progressive Deployment: Local → Staging → Production

Implement staged deployments to validate changes before production release.

LOCAL ENVIRONMENT (Development Machine)

# WHY: Test locally before any Azure deployment
# HOW: Use development configuration and local database
dotnet run --environment Development
# Verify: http://localhost:5000

STAGING ENVIRONMENT (Azure Deployment Slot)

# STEP 1: Create staging slot
# WHY: Staging slot mirrors production configuration for testing
# HOW: Separate slot with own URL and settings
az webapp deployment slot create \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --slot staging

# STEP 2: Deploy to staging
# WHY: Validate deployment without affecting production users
az webapp deploy \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --slot staging \
  --src-path app.zip \
  --type zip

# STEP 3: Test staging deployment
# Verify: https://webapp-vanna-blazor-prod-staging.azurewebsites.net

# STEP 4: Smoke test checklist
# - [ ] Application loads without errors
# - [ ] Authentication flow completes
# - [ ] Vanna AI query executes successfully
# - [ ] Database connectivity verified
# - [ ] Health check returns healthy status

PRODUCTION ENVIRONMENT (Swap from Staging)

# WHY: Slot swap provides zero-downtime deployment
# HOW: Azure routes traffic to staging, then swaps infrastructure
az webapp deployment slot swap \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --slot staging \
  --target-slot production

# STEP: Monitor production after swap
az webapp log tail \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod

💡 Tip: Configure auto-swap for fully automated deployments to staging that automatically promote to production after successful health checks. We use this for our internal systems but recommend manual swaps for customer-facing applications.

Step 5: Database Migration

Apply Entity Framework migrations to your production database.

Manual Migration Approach

# WHY: Explicit migration control for production databases
# HOW: Set connection string via environment variable

# STEP 1: Export production connection string
export ConnectionStrings__DefaultConnection="Server=tcp:sql-vanna-blazor-prod.database.windows.net,1433;Database=VannaBlazorDB;User ID=sqladmin;Password=YourSecurePassword123!;Encrypt=True;TrustServerCertificate=False;"

# STEP 2: Run migrations against production database
# WHY: Applies all pending migrations to bring schema up to date
dotnet ef database update \
  --connection "$ConnectionStrings__DefaultConnection" \
  --project src/VannaBlazorApp/VannaBlazorApp.csproj \
  --verbose

# STEP 3: Verify migration success
# Check that __EFMigrationsHistory table contains latest migrations

Automatic Migration Approach

Configure the application to run migrations automatically at startup.

FILE: Program.cs PURPOSE: Automatic database migration on application start

// WHY: Automatic migrations ensure database schema matches application
// HOW: Run migrations before application accepts requests
if (app.Environment.IsProduction())
{
    using (var scope = app.Services.CreateScope())
    {
        var services = scope.ServiceProvider;
        var logger = services.GetRequiredService<ILogger<Program>>();

        try
        {
            var context = services.GetRequiredService<ApplicationDbContext>();

            // WHY: Check for pending migrations before applying
            var pendingMigrations = await context.Database.GetPendingMigrationsAsync();

            if (pendingMigrations.Any())
            {
                logger.LogInformation(
                    "Applying {Count} pending migrations: {Migrations}",
                    pendingMigrations.Count(),
                    string.Join(", ", pendingMigrations));

                // WHY: Migrate synchronously to block startup until complete
                // HOW: Ensures schema ready before processing requests
                await context.Database.MigrateAsync();

                logger.LogInformation("Database migrations completed successfully");
            }
            else
            {
                logger.LogInformation("Database schema is up to date");
            }
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "An error occurred while migrating the database");

            // WHY: Fail fast if database migration fails
            // HOW: Prevents serving requests with incorrect schema
            throw;
        }
    }
}

⚠️ Warning: Automatic migrations are convenient but can cause deployment failures if migrations fail. For production environments, consider running migrations as a separate deployment step or using migration bundles for better control.

Step 6: Monitoring and Application Insights

Configure comprehensive monitoring to track application health and performance.

Create Application Insights Resource

# WHY: Application Insights provides telemetry and diagnostics
# HOW: Web application type optimizes for web app monitoring
az monitor app-insights component create \
  --app ai-vanna-blazor \
  --location eastus \
  --resource-group rg-vanna-blazor-prod \
  --application-type web

# WHY: Retrieve instrumentation key for application configuration
INSTRUMENTATION_KEY=$(az monitor app-insights component show \
  --app ai-vanna-blazor \
  --resource-group rg-vanna-blazor-prod \
  --query instrumentationKey \
  --output tsv)

# WHY: Configure App Service to send telemetry to Application Insights
# HOW: APPINSIGHTS_INSTRUMENTATIONKEY enables automatic instrumentation
az webapp config appsettings set \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --settings \
    APPINSIGHTS_INSTRUMENTATIONKEY="$INSTRUMENTATION_KEY" \
    APPLICATIONINSIGHTS_CONNECTION_STRING="InstrumentationKey=$INSTRUMENTATION_KEY"

Configure Application Insights in Code

FILE: Program.cs PURPOSE: Enable Application Insights telemetry

// WHY: AddApplicationInsightsTelemetry enables automatic telemetry collection
// HOW: Instruments HTTP requests, dependencies, exceptions automatically
builder.Services.AddApplicationInsightsTelemetry(options =>
{
    options.ConnectionString = builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"];
    // WHY: Enable adaptive sampling to control telemetry volume
    options.EnableAdaptiveSampling = true;
    // WHY: Track dependency calls to external services
    options.EnableDependencyTrackingTelemetryModule = true;
    // WHY: Capture detailed performance counters
    options.EnablePerformanceCounterCollectionModule = true;
});

Add Custom Telemetry for Vanna Queries

Track Vanna AI query performance and success rates.

FILE: Services/VannaService.cs PURPOSE: Custom telemetry for AI operations

public class VannaService : IVannaService
{
    private readonly HttpClient _httpClient;
    private readonly TelemetryClient _telemetryClient;
    private readonly ILogger<VannaService> _logger;

    public VannaService(
        HttpClient httpClient,
        TelemetryClient telemetryClient,
        ILogger<VannaService> logger)
    {
        _httpClient = httpClient;
        _telemetryClient = telemetryClient;
        _logger = logger;
    }

    public async Task<QueryResult> ExecuteQueryAsync(
        string naturalLanguage,
        CancellationToken cancellationToken = default)
    {
        // WHY: Track each Vanna query as a dependency operation
        // HOW: StartOperation creates correlation context for related telemetry
        using var operation = _telemetryClient.StartOperation<DependencyTelemetry>("Vanna AI Query");

        // Add custom properties for filtering and analysis
        operation.Telemetry.Type = "AI";
        operation.Telemetry.Data = naturalLanguage;
        operation.Telemetry.Target = "api.vanna.ai";

        try
        {
            var stopwatch = Stopwatch.StartNew();

            // Execute the actual query
            var result = await _vannaClient.QueryAsync(naturalLanguage, cancellationToken);

            stopwatch.Stop();

            // WHY: Mark operation as successful
            operation.Telemetry.Success = true;
            operation.Telemetry.Duration = stopwatch.Elapsed;

            // WHY: Track custom metrics for analysis
            // HOW: Metrics enable dashboards and alerts
            _telemetryClient.TrackMetric(
                "VannaAI.QueryLatency",
                stopwatch.ElapsedMilliseconds,
                new Dictionary<string, string>
                {
                    { "QueryComplexity", DetermineComplexity(naturalLanguage) },
                    { "ResultCount", result.Rows.Count.ToString() }
                });

            _telemetryClient.TrackEvent(
                "VannaAI.QuerySuccess",
                new Dictionary<string, string>
                {
                    { "QueryLength", naturalLanguage.Length.ToString() },
                    { "ExecutionTime", stopwatch.ElapsedMilliseconds.ToString() }
                });

            return result;
        }
        catch (HttpRequestException ex)
        {
            // WHY: Track failures separately for monitoring
            operation.Telemetry.Success = false;

            _telemetryClient.TrackException(ex, new Dictionary<string, string>
            {
                { "ErrorType", "NetworkError" },
                { "Query", naturalLanguage }
            });

            _logger.LogError(ex, "Vanna AI query failed due to network error");
            throw;
        }
        catch (Exception ex)
        {
            operation.Telemetry.Success = false;

            _telemetryClient.TrackException(ex, new Dictionary<string, string>
            {
                { "ErrorType", "QueryExecutionError" },
                { "Query", naturalLanguage }
            });

            _logger.LogError(ex, "Vanna AI query execution failed");
            throw;
        }
    }

    private string DetermineComplexity(string query)
    {
        // WHY: Classify query complexity for performance analysis
        var wordCount = query.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;

        return wordCount switch
        {
            <= 5 => "Simple",
            <= 15 => "Medium",
            _ => "Complex"
        };
    }
}

💡 Tip: Create custom Application Insights dashboards that show Vanna AI query success rates, average latency, and error patterns. This helps identify when model retraining is needed or API issues occur.

Troubleshooting Common Deployment Issues

Issue 1: SignalR Connection Failures

Symptoms:

  • Users experience frequent disconnections
  • Error: “WebSocket closed with status code: 1006”
  • Components not responding to real-time updates

Cause: WebSockets not enabled or load balancer doesn’t support sticky sessions

Solution:

# Verify WebSockets enabled
az webapp config show \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --query webSocketsEnabled

# Enable if disabled
az webapp config set \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --web-sockets-enabled true

# Verify Always On enabled (prevents idle timeout)
az webapp config show \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --query alwaysOn

If using Application Gateway or Front Door:

# Configure session affinity (sticky sessions)
az network application-gateway http-settings update \
  --resource-group rg-vanna-blazor-prod \
  --gateway-name myAppGateway \
  --name appGatewayBackendHttpSettings \
  --cookie-based-affinity Enabled

Issue 2: Vanna AI Timeout Errors

Symptoms:

  • Error: “A task was canceled”
  • Error: “The operation was canceled”
  • Complex queries fail while simple queries succeed

Cause: Default HTTP client timeout too short for complex queries

Solution:

FILE: Program.cs PURPOSE: Configure Vanna HTTP client with appropriate timeout

// WHY: Complex queries may take longer than default 100 second timeout
// HOW: Configure named HttpClient with extended timeout
builder.Services.AddHttpClient<IVannaService, VannaService>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["VannaAI:BaseUrl"]!);
    // WHY: Extended timeout for complex AI queries
    client.Timeout = TimeSpan.FromSeconds(
        builder.Configuration.GetValue<int>("VannaAI:TimeoutSeconds", 120));
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
    // WHY: Allow automatic decompression for better performance
    AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate
})
// WHY: Retry transient failures automatically
.AddTransientHttpErrorPolicy(policyBuilder =>
    policyBuilder.WaitAndRetryAsync(3, retryAttempt =>
        TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));

Issue 3: Database Connection Pool Exhaustion

Symptoms:

  • Error: “Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool”
  • Application slows down under load
  • Connections not being released

Cause: Connection pool exhausted due to not disposing contexts or too small pool size

Solution:

Check connection string configuration:

// WHY: Adjust connection pool settings for application workload
// HOW: Add pool parameters to connection string
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

// Add these parameters if not present:
// Min Pool Size=10          - Maintain minimum ready connections
// Max Pool Size=100         - Allow up to 100 concurrent connections
// Connection Timeout=30     - Wait 30 seconds for connection
// Connection Lifetime=600   - Recycle connections after 10 minutes

Ensure proper DbContext disposal:

// ❌ PROBLEM: Context not disposed
public async Task<List<Query>> GetQueriesAsync()
{
    var context = new ApplicationDbContext();
    return await context.Queries.ToListAsync();
}

// ✅ SOLUTION: Use dependency injection and automatic disposal
public class QueryService
{
    private readonly ApplicationDbContext _context;

    public QueryService(ApplicationDbContext context)
    {
        _context = context; // Disposed by DI container
    }

    public async Task<List<Query>> GetQueriesAsync()
    {
        return await _context.Queries.ToListAsync();
    }
}

Issue 4: Managed Identity Authentication Failures

Symptoms:

  • Error: “Azure Key Vault Secret Not Found”
  • Error: “ManagedIdentityCredential authentication failed”
  • Configuration values not loading from Key Vault

Cause: Managed identity not granted proper permissions or Key Vault URL incorrect

Solution:

# Verify managed identity is enabled
az webapp identity show \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod

# Check Key Vault access policies
az keyvault show \
  --name kv-vanna-blazor-prod \
  --resource-group rg-vanna-blazor-prod \
  --query properties.accessPolicies

# Re-grant permissions if missing
PRINCIPAL_ID=$(az webapp identity show \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --query principalId \
  --output tsv)

az keyvault set-policy \
  --name kv-vanna-blazor-prod \
  --object-id $PRINCIPAL_ID \
  --secret-permissions get list

Verify Key Vault URL is correct:

# Check app setting
az webapp config appsettings list \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --query "[?name=='KeyVaultUrl'].value" \
  --output tsv

# Should return: https://kv-vanna-blazor-prod.vault.azure.net/

Issue 5: Deployment Succeeds but Application Won’t Start

Symptoms:

  • HTTP 500.30 - ASP.NET Core app failed to start
  • Health checks failing
  • Application logs show startup exceptions

Cause: Missing environment variables, incorrect configuration, or dependency issues

Solution:

Check application logs:

# View real-time application logs
az webapp log tail \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod

# Download recent logs for analysis
az webapp log download \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --log-file deployment-logs.zip

Common configuration issues to check:

  1. Missing app settings:
# Verify all required settings present
az webapp config appsettings list \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod
  1. Incorrect connection strings:
# Test database connectivity from Azure
az sql db show-connection-string \
  --client ado.net \
  --name VannaBlazorDB \
  --server sql-vanna-blazor-prod
  1. Runtime version mismatch:
# Verify runtime matches your application
az webapp config show \
  --resource-group rg-vanna-blazor-prod \
  --name webapp-vanna-blazor-prod \
  --query linuxFxVersion

Production Deployment Checklist

Use this checklist to ensure comprehensive deployment preparation.

Infrastructure Setup

  • Resource group created in appropriate region
  • App Service Plan created with S1 or higher tier
  • Azure SQL Database created and firewall configured
  • Key Vault created with access policies configured
  • Application Insights resource created
  • Managed identity enabled for App Service

Application Configuration

  • Production appsettings.json configured
  • Health checks implemented for all dependencies
  • Azure-integrated logging configured
  • Key Vault integration tested
  • Database connection strings in Key Vault
  • Vanna AI API key securely stored

Security Configuration

  • HTTPS enforced (no HTTP access)
  • Custom domain configured with valid SSL
  • Managed identity permissions verified
  • SQL Database firewall rules appropriate for environment
  • Key Vault audit logging enabled
  • Application authentication configured

Performance Configuration

  • Always On enabled
  • WebSockets enabled for SignalR
  • Connection pool settings optimized
  • HTTP client timeouts configured for Vanna AI
  • Application Insights sampling configured
  • Health check endpoints responding

Deployment Preparation

  • Database migrations tested in staging
  • Publish profile created and tested
  • Deployment slot created (if using staged deployment)
  • Rollback plan documented
  • Team notified of deployment window

Post-Deployment Validation

  • Application loads without errors
  • Health check endpoint returns healthy status
  • Authentication flow completes successfully
  • Vanna AI integration executes test query
  • Database connectivity verified
  • SignalR real-time updates working
  • Application Insights receiving telemetry
  • Error pages display correctly
  • Performance acceptable under load

Monitoring Setup

  • Application Insights dashboard created
  • Alerts configured for critical failures
  • Availability tests configured
  • Custom metrics tracked for Vanna queries
  • Log Analytics workspace linked
  • Team notification channels configured

💡 Tip: Create this checklist as a work item template in Azure DevOps or GitHub Issues. Track deployment readiness and maintain audit history of production deployments.

Frequently Asked Questions

Q: Should I use automatic or manual database migrations?

A: It depends on your deployment process and risk tolerance.

Use automatic migrations when:

  • You have simple schema changes
  • You deploy infrequently
  • Staging environment mirrors production
  • Team has limited database administration experience

Use manual migrations when:

  • Schema changes are complex or involve data transformation
  • You require explicit approval before production schema changes
  • Migrations need to run during maintenance windows
  • You need to validate migrations in production before deploying code

In our government deployments, we use manual migrations for production but automatic migrations for development and staging environments.

Q: What tier should I choose for my App Service Plan?

A: The minimum for production Blazor Server is S1 Standard, but consider your requirements:

TierUse WhenLimitations
B1-B3 (Basic)Never for production Blazor ServerNo Always On, limited scaling
S1 (Standard)Small production apps, 1-3 serversLimited auto-scale
S2-S3 (Standard)Medium production, 10,000+ usersBetter performance
P1V2-P3V2 (Premium)Enterprise, compliance requirementsHigher cost
I1-I3 (Isolated)Government, network isolationHighest cost

Q: How do I handle Vanna AI rate limits?

A: Implement caching and request throttling:

// Cache frequent queries
builder.Services.AddMemoryCache();
builder.Services.Decorate<IVannaService, CachedVannaService>();

// Implement circuit breaker for API failures
builder.Services.AddHttpClient<IVannaService, VannaService>()
    .AddPolicyHandler(Policy
        .Handle<HttpRequestException>()
        .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

Q: Should I use deployment slots or direct deployment?

A: Use deployment slots for production environments:

Advantages:

  • Zero-downtime deployments
  • Production testing before swap
  • Easy rollback by swapping back
  • Gradual traffic migration options

Deployment slots add minimal cost (approximately 10% of your app service plan cost) but provide significant risk reduction. Every production deployment we manage uses slots.

Q: How do I monitor Vanna AI costs and usage?

A: Track usage through custom telemetry and Application Insights:

_telemetryClient.TrackMetric("VannaAI.APICall", 1, new Dictionary<string, string>
{
    { "Operation", "Query" },
    { "User", userId },
    { "Timestamp", DateTime.UtcNow.ToString("o") }
});

Create a scheduled function or WebJob to aggregate daily usage and compare against your Vanna AI plan limits.

Q: What’s the best way to handle configuration for multiple environments?

A: Use this layered approach:

  1. appsettings.json - Default values and structure
  2. appsettings.{Environment}.json - Environment overrides
  3. Azure App Settings - Instance-specific values
  4. Azure Key Vault - Secrets and sensitive configuration

This hierarchy allows you to:

  • Version control non-sensitive defaults
  • Override per environment
  • Secure sensitive values in Key Vault
  • Adjust configuration without redeployment

Next Steps

Now that your application is deployed, consider these enhancements:

Immediate Actions:

  1. Configure Alerts - Set up Application Insights alerts for failures and performance degradation
  2. Create Dashboards - Build monitoring dashboards for stakeholders
  3. Document Runbooks - Create operational procedures for common scenarios
  4. Schedule Reviews - Plan weekly review of telemetry and user feedback

Short-term Improvements (1-2 weeks):

  1. Implement Caching - Add response caching for frequent Vanna queries
  2. Performance Testing - Load test to validate scaling configurations
  3. Security Audit - Review permissions and access controls
  4. Backup Strategy - Implement and test database backup and restore procedures

Medium-term Enhancements (1-3 months):

  1. Multi-region Deployment - Configure geo-distributed deployments for resilience
  2. Advanced Monitoring - Implement custom metrics and user telemetry
  3. Query Analytics - Build dashboards showing query patterns and accuracy
  4. Cost Optimization - Review and optimize Azure resource utilization

Long-term Evolution (3-6 months):

  1. AI Model Refinement - Use production queries to improve Vanna training
  2. Feature Flags - Implement feature toggles using Azure App Configuration
  3. Disaster Recovery - Complete DR plan with regular testing
  4. Compliance Documentation - Maintain audit logs and compliance evidence

Key Takeaways

Do:

  • Use S1 tier or higher for production Blazor Server applications
  • Enable Always On and WebSockets for SignalR reliability
  • Store all secrets in Azure Key Vault with managed identity
  • Implement comprehensive health checks for dependencies
  • Use deployment slots for zero-downtime production deployments
  • Configure Application Insights for monitoring and diagnostics
  • Test migrations in staging before production deployment

Don’t:

  • Deploy Blazor Server on Basic tier (missing Always On)
  • Store connection strings or API keys in appsettings.json
  • Use automatic migrations without testing in staging
  • Deploy directly to production without validation
  • Ignore Application Insights recommendations
  • Deploy without rollback plan
  • Skip the deployment checklist

💡 Remember:

  • Deployment is iterative - monitor and improve based on real usage
  • Start with staging deployments to validate changes
  • Document your specific configuration decisions
  • Plan for failure - implement monitoring and alerts early
  • Review security and performance regularly

Need Help?

Deploying AI-powered applications to production requires expertise across multiple domains - cloud infrastructure, application architecture, security, and AI integration. Based on experience deploying similar systems for government agencies, we understand the complexities involved.

If you’re implementing this for an enterprise system with specific compliance requirements, security constraints, or scale needs, let’s discuss your requirements. We’ve helped organizations navigate these exact challenges, from initial architecture through FedRAMP compliance.

Schedule a consultation to discuss your deployment strategy.

Additional Resources:


This deployment guide reflects production patterns used in federal government systems serving large user bases. Your specific requirements may vary based on scale, compliance needs, and infrastructure constraints.

Back to Blog

Related Posts

View All Posts »
Securing Vanna AI in Blazor: Authentication and Data Protection

Securing Vanna AI in Blazor: Authentication and Data Protection

After managing 10+ federal systems with AI components, I've learned that securing AI-powered database queries requires more than just authentication. Here's how to implement comprehensive security controls that protect sensitive data while maintaining the power of natural language queries.