Skip to content

Azure Database Initialization Guide ​

Version: 1.0 Last Updated: 2025-12-23 Status: Production Ready


Overview ​

This document explains how database initialization works in Azure Container Apps deployment vs local Docker, and provides step-by-step instructions for deploying Sorcha to Azure with proper database setup.


Database Architecture ​

Services and Databases ​

ServiceDatabaseTechnologyPurpose
Wallet Servicesorcha_walletPostgreSQL 17Wallet persistence, transactions
Tenant Servicesorcha_tenantPostgreSQL 17Organizations, users, auth
Register Servicesorcha_system_registerCosmos DB (MongoDB API)Transaction ledger
Peer Servicesorcha_system_registerCosmos DB (MongoDB API)P2P node registry

Local Docker vs Azure Deployment ​

Local Docker (docker-compose.yml) ​

yaml
# Local approach uses PostgreSQL container initialization scripts
postgres:
  image: postgres:17-alpine
  volumes:
    - ./docker/postgres-init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro

How it works:

  1. PostgreSQL container starts
  2. Initialization script (postgres-init.sql) runs once when volume is empty
  3. Script creates sorcha_wallet and sorcha_tenant databases
  4. Services start and EF Core migrations run automatically via DatabaseInitializer HostedService

Limitations:

  • Only runs on first startup (when volume is empty)
  • Requires volume deletion to re-run initialization
  • Not applicable to Azure managed databases

Azure Container Apps Deployment ​

bicep
// Azure approach uses Bicep templates to create databases
resource walletDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-12-01-preview' = {
  parent: postgresServer
  name: 'sorcha_wallet'
  properties: {
    charset: 'UTF8'
    collation: 'en_US.utf8'
  }
}

How it works:

  1. Infrastructure as Code (Bicep) creates:
    • Azure Database for PostgreSQL Flexible Server
    • Databases: sorcha_wallet, sorcha_tenant
    • Azure Cosmos DB with MongoDB API
    • Database: sorcha_system_register
  2. Container Apps deploy with database connection strings
  3. EF Core migrations run automatically on service startup
  4. Tenant Service seeds default organization and service principals

Azure Database Resources ​

PostgreSQL Flexible Server ​

Configuration:

  • SKU: Standard_B1ms (Burstable) - 1 vCore, 2GB RAM
  • Version: PostgreSQL 17
  • Storage: 32GB (with auto-grow enabled)
  • Backup: 7-day retention, no geo-redundancy
  • Cost: ~$12/month (Burstable tier)

Databases Created:

  • sorcha_wallet - Wallet Service data
  • sorcha_tenant - Tenant/Auth Service data

Firewall Rules:

  • Allows access from Azure services (Container Apps)
  • SSL/TLS required for connections

Cosmos DB (MongoDB API) ​

Configuration:

  • Tier: Serverless (pay-per-request)
  • API: MongoDB 6.0 compatibility
  • Consistency: Session level
  • Cost: Pay-per-request (~$0.25/million operations)

Database Created:

  • sorcha_system_register - Transaction ledger

Collections:

  • transactions - Sharded by transaction ID

Automatic Migration Process ​

EF Core Migration Flow ​

Both Wallet and Tenant services use automatic migrations on startup:

Wallet Service ​

csharp
// src/Services/Sorcha.Wallet.Service/Program.cs
await app.Services.ApplyWalletDatabaseMigrationsAsync();

What happens:

  1. Service starts
  2. Checks if WalletDbContext is configured
  3. Runs context.Database.MigrateAsync()
  4. Creates tables if they don't exist
  5. Applies any pending migrations

Tenant Service ​

csharp
// src/Services/Sorcha.Tenant.Service/Extensions/ServiceCollectionExtensions.cs
builder.Services.AddDatabaseInitializer();

What happens:

  1. DatabaseInitializerHostedService runs on startup
  2. Runs context.Database.MigrateAsync()
  3. Seeds default organization (sorcha.local)
  4. Creates default admin user (admin@sorcha.local)
  5. Creates service principals for all services

Default Credentials (Change in Production!):

  • Email: admin@sorcha.local
  • Password: Dev_Pass_2025!

Deployment Instructions ​

Prerequisites ​

  1. Azure Subscription with permissions to create resources
  2. Azure CLI installed and authenticated
  3. Docker installed for building container images
  4. PostgreSQL password for admin account

Step 1: Set Deployment Parameters ​

bash
# Set environment variables
export RESOURCE_GROUP="sorcha-prod-rg"
export LOCATION="eastus"
export CONTAINER_REGISTRY="sorchacr"
export ENVIRONMENT="prod"

# Generate secure PostgreSQL password
export POSTGRES_PASSWORD=$(openssl rand -base64 32)
echo "PostgreSQL Password: $POSTGRES_PASSWORD" # SAVE THIS!

Step 2: Deploy Infrastructure ​

bash
# Deploy all infrastructure (databases + container apps)
az deployment sub create \
  --location $LOCATION \
  --template-file infra/main.bicep \
  --parameters \
    resourceGroupName=$RESOURCE_GROUP \
    location=$LOCATION \
    containerRegistryName=$CONTAINER_REGISTRY \
    environment=$ENVIRONMENT \
    postgresAdminPassword=$POSTGRES_PASSWORD

What this creates:

  • Resource Group
  • Azure Database for PostgreSQL Flexible Server
    • sorcha_wallet database
    • sorcha_tenant database
  • Azure Cosmos DB (MongoDB API)
    • sorcha_system_register database
  • Azure Cache for Redis
  • Azure Container Registry
  • Container Apps Environment
  • All Container Apps (but not started yet - no images)

Step 3: Build and Push Container Images ​

bash
# Login to Azure Container Registry
az acr login --name $CONTAINER_REGISTRY

# Build and push all service images
docker-compose build
docker tag sorcha/wallet-service:latest $CONTAINER_REGISTRY.azurecr.io/wallet-service:latest
docker tag sorcha/tenant-service:latest $CONTAINER_REGISTRY.azurecr.io/tenant-service:latest
docker tag sorcha/blueprint-service:latest $CONTAINER_REGISTRY.azurecr.io/blueprint-api:latest
docker tag sorcha/peer-service:latest $CONTAINER_REGISTRY.azurecr.io/peer-service:latest
docker tag sorcha/api-gateway:latest $CONTAINER_REGISTRY.azurecr.io/api-gateway:latest

docker push $CONTAINER_REGISTRY.azurecr.io/wallet-service:latest
docker push $CONTAINER_REGISTRY.azurecr.io/tenant-service:latest
docker push $CONTAINER_REGISTRY.azurecr.io/blueprint-api:latest
docker push $CONTAINER_REGISTRY.azurecr.io/peer-service:latest
docker push $CONTAINER_REGISTRY.azurecr.io/api-gateway:latest

Step 4: Restart Container Apps ​

bash
# Restart all apps to pull new images and run migrations
az containerapp restart --name wallet-service --resource-group $RESOURCE_GROUP
az containerapp restart --name tenant-service --resource-group $RESOURCE_GROUP
az containerapp restart --name blueprint-api --resource-group $RESOURCE_GROUP
az containerapp restart --name peer-service --resource-group $RESOURCE_GROUP
az containerapp restart --name api-gateway --resource-group $RESOURCE_GROUP

Step 5: Verify Database Initialization ​

bash
# Check Tenant Service logs for database initialization
az containerapp logs show \
  --name tenant-service \
  --resource-group $RESOURCE_GROUP \
  --tail 100 \
  --follow

Look for:

[INF] Applying 2 pending migration(s): 20251210174739_InitialCreate, 20251223153922_UpdateBrandingToJson
[INF] Database migrations applied successfully
[INF] Creating default organization: Sorcha Local
[INF] Default organization created with ID: 00000000-0000-0000-0000-000000000001
[INF] Creating default administrator: admin@sorcha.local
[INF] Default administrator created with ID: 00000000-0000-0000-0001-000000000001
[WRN] Default admin credentials - Email: admin@sorcha.local, Password: Dev_Pass_2025!
[INF] Creating service principal: Blueprint Service
[INF] Creating service principal: Wallet Service
[INF] Creating service principal: Register Service
[INF] Creating service principal: Peer Service
[INF] Database initialization completed successfully

Save the service principal credentials shown in the logs!

bash
# Check Wallet Service logs
az containerapp logs show \
  --name wallet-service \
  --resource-group $RESOURCE_GROUP \
  --tail 50

Look for:

Applying Wallet Service database migrations...
Wallet Service database migrations applied successfully

Step 6: Get Application URLs ​

bash
# Get API Gateway URL
az containerapp show \
  --name api-gateway \
  --resource-group $RESOURCE_GROUP \
  --query properties.configuration.ingress.fqdn \
  --output tsv

Database Connection Management ​

Connection Strings in Azure ​

Connection strings are stored as Container App secrets and passed as environment variables:

bicep
secrets: [
  {
    name: 'wallet-db-connection'
    value: 'Host=sorcha-postgres-prod.postgres.database.azure.com;Database=sorcha_wallet;Username=sorcha_admin;Password=***;SslMode=Require'
  }
]

env: [
  {
    name: 'ConnectionStrings__wallet-db'
    secretRef: 'wallet-db-connection'
  }
]

Best Practices ​

Development/Staging:

  • Use Bicep template outputs for connection strings
  • Store in Container App secrets

Production:

  • Use Azure Key Vault for connection strings
  • Enable Managed Identity for Container Apps
  • Reference Key Vault secrets in Container Apps:
bicep
// Future improvement: Key Vault integration
secrets: [
  {
    name: 'wallet-db-connection'
    keyVaultUrl: 'https://sorcha-kv.vault.azure.net/secrets/wallet-db-connection'
    identity: 'system'
  }
]

Database Maintenance ​

Applying New Migrations ​

Scenario: You've created a new EF Core migration locally and need to apply it in Azure.

Steps:

  1. Create migration locally:

    bash
    dotnet ef migrations add YourMigrationName --context WalletDbContext
  2. Build and push new image:

    bash
    docker-compose build wallet-service
    docker tag sorcha/wallet-service:latest $CONTAINER_REGISTRY.azurecr.io/wallet-service:latest
    docker push $CONTAINER_REGISTRY.azurecr.io/wallet-service:latest
  3. Restart Container App:

    bash
    az containerapp restart --name wallet-service --resource-group $RESOURCE_GROUP
  4. Verify migration applied:

    bash
    az containerapp logs show --name wallet-service --resource-group $RESOURCE_GROUP --tail 50

The migration runs automatically on startup - no manual database access required!

Manual Database Access ​

Connect to Azure PostgreSQL:

bash
# Get PostgreSQL FQDN
POSTGRES_FQDN=$(az postgres flexible-server show \
  --resource-group $RESOURCE_GROUP \
  --name sorcha-postgres-prod \
  --query fullyQualifiedDomainName \
  --output tsv)

# Connect with psql
psql "host=$POSTGRES_FQDN port=5432 dbname=sorcha_wallet user=sorcha_admin password=$POSTGRES_PASSWORD sslmode=require"

Connect to Cosmos DB (MongoDB):

bash
# Get MongoDB connection string
MONGO_CONN=$(az cosmosdb keys list \
  --name sorcha-cosmos-prod \
  --resource-group $RESOURCE_GROUP \
  --type connection-strings \
  --query "connectionStrings[0].connectionString" \
  --output tsv)

# Connect with mongosh
mongosh "$MONGO_CONN"

Troubleshooting ​

Problem: Migrations Not Running ​

Symptoms:

  • Service starts but tables don't exist
  • Errors about missing tables in logs

Solution:

bash
# Check if database connection is configured
az containerapp logs show --name wallet-service --resource-group $RESOURCE_GROUP | grep "migration"

# Verify connection string secret exists
az containerapp secret list --name wallet-service --resource-group $RESOURCE_GROUP

# Restart to force migration retry
az containerapp restart --name wallet-service --resource-group $RESOURCE_GROUP

Problem: Connection Timeout ​

Symptoms:

Npgsql.NpgsqlException: Timeout during connection attempt

Solution:

bash
# Verify PostgreSQL firewall rules
az postgres flexible-server firewall-rule list \
  --resource-group $RESOURCE_GROUP \
  --name sorcha-postgres-prod

# Add rule to allow Azure services (should already exist)
az postgres flexible-server firewall-rule create \
  --resource-group $RESOURCE_GROUP \
  --name sorcha-postgres-prod \
  --rule-name AllowAzureServices \
  --start-ip-address 0.0.0.0 \
  --end-ip-address 0.0.0.0

Problem: Authentication Failed ​

Symptoms:

password authentication failed for user "sorcha_admin"

Solution:

bash
# Update PostgreSQL admin password
az postgres flexible-server update \
  --resource-group $RESOURCE_GROUP \
  --name sorcha-postgres-prod \
  --admin-password $NEW_PASSWORD

# Update Container App secret
az containerapp secret set \
  --name wallet-service \
  --resource-group $RESOURCE_GROUP \
  --secrets wallet-db-connection="Host=...;Password=$NEW_PASSWORD;..."

# Restart service
az containerapp restart --name wallet-service --resource-group $RESOURCE_GROUP

Problem: Tenant Service Not Seeding Data ​

Symptoms:

  • No default organization created
  • Can't login with default credentials

Solution:

bash
# Check if database is empty
az containerapp exec \
  --name tenant-service \
  --resource-group $RESOURCE_GROUP \
  --command "dotnet ef database update"

# Delete and recreate database (CAUTION: deletes all data!)
az postgres flexible-server db delete \
  --resource-group $RESOURCE_GROUP \
  --server-name sorcha-postgres-prod \
  --database-name sorcha_tenant

az postgres flexible-server db create \
  --resource-group $RESOURCE_GROUP \
  --server-name sorcha-postgres-prod \
  --database-name sorcha_tenant

# Restart to trigger re-initialization
az containerapp restart --name tenant-service --resource-group $RESOURCE_GROUP

Cost Optimization ​

Database Costs ​

ResourceConfigurationMonthly Cost (Estimate)
PostgreSQL Flexible ServerStandard_B1ms (Burstable)~$12
Cosmos DBServerless (low usage)~$1-5
Redis CacheBasic C0 (250MB)~$16
Container AppsConsumption (100 requests/day)~$5
Container RegistryBasic~$5
Total~$39-43/month

Cost Saving Tips ​

  1. Use Free Tier for Development:

    bicep
    enableFreeTier: true // One free Cosmos DB per subscription
  2. Stop non-production resources:

    bash
    # Stop PostgreSQL when not in use
    az postgres flexible-server stop \
      --resource-group $RESOURCE_GROUP \
      --name sorcha-postgres-prod
  3. Scale down Container Apps:

    bicep
    minReplicas: 0 // Scale to zero when idle
  4. Use reserved capacity for production:

    • PostgreSQL Reserved Capacity: Save up to 55%
    • Cosmos DB Reserved Capacity: Save up to 65%

Security Considerations ​

Secrets Management ​

Current Implementation (Development):

  • Connection strings stored in Container App secrets
  • Passwords passed via Bicep parameters

Production Recommendations:

  1. Use Azure Key Vault:

    • Store all connection strings in Key Vault
    • Use Managed Identity for Container Apps
    • Reference secrets via Key Vault URIs
  2. Rotate Credentials Regularly:

    bash
    # Rotate PostgreSQL password quarterly
    az postgres flexible-server update \
      --resource-group $RESOURCE_GROUP \
      --name sorcha-postgres-prod \
      --admin-password $(openssl rand -base64 32)
  3. Enable Advanced Threat Protection:

    bash
    az postgres flexible-server update \
      --resource-group $RESOURCE_GROUP \
      --name sorcha-postgres-prod \
      --threat-detection-enabled true
  4. Audit Database Access:

    • Enable PostgreSQL audit logging
    • Send logs to Log Analytics workspace
    • Set up alerts for suspicious activity

Network Security ​

Current Implementation:

  • Public endpoint with firewall rules
  • SSL/TLS required for all connections

Production Recommendations:

  1. Use Private Endpoints:

    bicep
    publicNetworkAccess: 'Disabled'
  2. VNet Integration:

    • Deploy Container Apps in VNet
    • Use Private Link for PostgreSQL
    • Use Service Endpoints for Cosmos DB

Summary: Local vs Azure ​

AspectLocal DockerAzure Container Apps
Database CreationPostgreSQL init scriptBicep infrastructure template
MigrationsEF Core auto-migrate on startupEF Core auto-migrate on startup
Seed DataHostedService (DatabaseInitializer)HostedService (DatabaseInitializer)
Connection Stringsdocker-compose environment varsContainer App secrets
Cost$0 (local resources)~$40/month (Azure resources)
BackupManual (volume snapshots)Automated (7-day retention)
ScalingManual container restartAuto-scale (0-5 replicas)
High AvailabilitySingle instanceMulti-zone (if enabled)

Key Takeaway: The database initialization logic is identical in both environments - it's just the infrastructure provisioning that differs (Docker init script vs Bicep templates).


Next Steps ​

  1. ✅ Review this documentation
  2. ✅ Prepare Azure subscription and credentials
  3. ✅ Run deployment steps (Step 1-6 above)
  4. ✅ Verify database initialization in logs
  5. ✅ Save service principal credentials
  6. ✅ Change default admin password
  7. ✅ Configure production secrets in Key Vault
  8. ✅ Set up monitoring and alerts

Questions or Issues? See TROUBLESHOOTING.md or create a GitHub issue.

Released under the MIT License.