Skip to content

SQL Server

Microtec ERP uses a SQL Server VM in the dedicated mic-backend-shared-sql-rg resource group. This single SQL Server instance hosts all tenant databases, the admin database, and the Keycloak database across all environments except production (which uses a separate, hardened instance).


Server Details

PropertyValue
Resource Groupmic-backend-shared-sql-rg
VM Namemic-shared-sql
Public IP20.50.120.95
SSH Accessssh -i ~/.ssh/mic-shared-sql sqladmin@20.50.120.95
SQL Port1433
SQL EditionSQL Server 2022 Developer / Standard
RegionUK South

Shared Across Environments

This SQL Server VM is shared by dev, stage, preprod, and UAT environments. It is intentionally NOT managed by the per-environment Bicep infrastructure (it predates the Bicep refactor). Do NOT include it in deprovision.yml — destroying it would affect all environments simultaneously.


Database Inventory

Database NamePurposeUsed By
microtec_adminCross-tenant admin data (companies, branches, users)All backend services, Keycloak mappers
keycloak_devKeycloak SSO data for devKeycloak (dev)
keycloak_stageKeycloak SSO data for stageKeycloak (stage)
keycloak_preprodKeycloak SSO data for preprodKeycloak (preprod)
keycloak_uatKeycloak SSO data for UATKeycloak (UAT)
tenant_{id}Per-tenant ERP data (one DB per company)Tenant-specific services

The tenant databases follow the pattern tenant_{tenantId} where tenantId is the integer primary key from microtec_admin.companies.


Database-per-Tenant Pattern

The backend's ITenantProvider reads the JWT companyId claim and resolves the connection string from a lookup table in microtec_admin:

csharp
// TenantProvider.cs
public async Task<string> GetConnectionStringAsync(int companyId)
{
    // Admin DB lookup: returns "Server=...;Database=tenant_{id};..."
    return await _adminRepository.GetTenantConnectionStringAsync(companyId);
}

Connection String Format

Admin Database

Server=20.50.120.95,1433;Database=microtec_admin;
User Id=admin_svc;Password={from-kv};
Encrypt=True;TrustServerCertificate=True;
MultipleActiveResultSets=True;Connection Timeout=30;

Tenant Database (resolved at runtime)

Server=20.50.120.95,1433;Database=tenant_{id};
User Id=tenant_svc;Password={from-kv};
Encrypt=True;TrustServerCertificate=True;
MultipleActiveResultSets=True;Connection Timeout=30;
Application Name={ServiceName};

TrustServerCertificate

TrustServerCertificate=True is required because the SQL VM uses a self-signed certificate. All traffic is encrypted in transit (SSL/TLS) — this setting only bypasses CN validation, not encryption.

Key Vault Secret Names

Secret NameConnection String
ConnectionStrings--AdminAdmin database
ConnectionStrings--DefaultTemplate for tenant DB (resolved at runtime)
Keycloak--DbPasswordKeycloak service account password

SQL Service Accounts

AccountPermissionsUsed By
sqladminsysadmin (VM OS account)SSH access, DBA operations
admin_svcdb_datawriter, db_datareader on microtec_adminBackend services (admin queries)
tenant_svcdb_owner on all tenant_* DBsBackend services (tenant operations)
sadb_owner on Microtec.SSO DBKeycloak (schema management)
readonly_svcdb_datareader on all DBsReporting, Seq, analytics

Creating a New Tenant Database

When a new company is provisioned, the Business Owner portal runs a migration script to create tenant_{newId} and grant tenant_svc ownership. This is handled by the Microtec provisioning workflow — do not create tenant databases manually.


DataProtection Key Storage

DataProtection Keys Are Machine-Specific

ASP.NET Core DataProtection keys are stored in the admin database (microtec_admin.DataProtectionKeys table). These keys encrypt cookies and session tokens. Keys encrypted on one server cannot be decrypted on a different server unless the key ring is shared.

If you migrate to a new SQL VM, export the DataProtectionKeys table and import it into the new instance before switching connection strings. Failure to do so will immediately invalidate all active user sessions.


Backup and Recovery

Backup TypeFrequencyRetention
Full backupDaily (2:00 AM GST)30 days
DifferentialEvery 6 hours7 days
Transaction logEvery 15 minutes3 days

Backups are stored in Azure Blob Storage in the mic-backend-shared-sql-rg storage account. Point-in-time restore is possible up to 3 days back.

bash
# SSH to SQL Server VM
ssh -i ~/.ssh/mic-shared-sql sqladmin@20.50.120.95

# Check last backup time
/opt/mssql-tools/bin/sqlcmd -S localhost -U sqladmin -Q \
  "SELECT database_name, backup_finish_date, type
   FROM msdb.dbo.backupset
   WHERE backup_finish_date > DATEADD(DAY,-1,GETDATE())
   ORDER BY backup_finish_date DESC"

Common DBA Operations

Create a New Tenant Database

sql
-- Run via SSH: sqlcmd -S localhost -U sqladmin
CREATE DATABASE tenant_99;
GO
USE tenant_99;
CREATE USER tenant_svc FOR LOGIN tenant_svc;
ALTER ROLE db_owner ADD MEMBER tenant_svc;
GO

Check Active Connections

sql
SELECT DB_NAME(dbid) AS DatabaseName, COUNT(*) AS ConnectionCount
FROM sys.sysprocesses
WHERE dbid > 0
GROUP BY DB_NAME(dbid)
ORDER BY ConnectionCount DESC;

Kill Blocking Sessions

bash
# Note: SA password contains '!' — use Python to avoid shell escaping issues
python3 -c "
import subprocess
subprocess.run([
    '/opt/mssql-tools/bin/sqlcmd', '-S', 'localhost', '-U', 'sqladmin',
    '-P', 'ActualPassword!',
    '-Q', 'KILL {blocking_spid}'
])
"

Shell Escaping — SA Password

If the sqladmin password contains special characters (!, $, etc.), do NOT pass it directly in a bash heredoc or double-quoted string. Use Python's subprocess.run() with a list argument (shown above) or a single-quoted bash string with no variable interpolation.


Connectivity from Container Apps

Container Apps reach the SQL VM via private endpoint or direct IP:

  • Dev/Stage: Direct IP (20.50.120.95) over the internet with TLS
  • PrepProd/UAT: Via VNet peering to the SQL VM VNet
  • Production: Via dedicated private endpoint in mic-backend-shared-sql-rg

The SQL port 1433 is open in the NSG only to Container App outbound IPs (NAT Gateway public IP per environment).


Internal Documentation — Microtec Platform Team