Appearance
Health Checks
Every Microtec microservice exposes standardised health check endpoints following the ASP.NET Core Health Checks framework. Container App Environments and Azure Front Door use these endpoints to determine service availability.
Endpoint Conventions
| Endpoint | Purpose | Returns |
|---|---|---|
/health | Aggregated health (all checks) | 200 Healthy / 503 Unhealthy |
/health/ready | Readiness — service ready to serve traffic | 200 Ready / 503 Not Ready |
/health/live | Liveness — process is alive (not deadlocked) | 200 Alive / 503 Dead |
Liveness vs Readiness
Liveness answers "is the process still running?". If liveness fails, the Container App runtime restarts the container.
Readiness answers "is the service ready to serve requests?". If readiness fails, the Container App runtime removes the replica from the load balancer but does NOT restart it. This is used during startup (waiting for DB connections) or during rolling restarts.
Use separate probes for each purpose. A failing DB connection should make the service not ready, but should not cause the container to restart unnecessarily.
Response Format
Health check responses follow the standard ASP.NET Core format:
Healthy (HTTP 200)
json
{
"status": "Healthy",
"results": {
"database": {
"status": "Healthy",
"description": "SQL Server connection verified",
"duration": "00:00:00.0124563"
},
"redis": {
"status": "Healthy",
"description": "Redis PING successful",
"duration": "00:00:00.0031234"
},
"seq": {
"status": "Healthy",
"description": "Seq log ingestion reachable",
"duration": "00:00:00.0089012"
}
}
}Unhealthy (HTTP 503)
json
{
"status": "Unhealthy",
"results": {
"database": {
"status": "Unhealthy",
"description": "Connection timeout after 30s",
"exception": "Microsoft.Data.SqlClient.SqlException: ..."
},
"redis": {
"status": "Healthy",
"description": "Redis PING successful"
}
}
}Implementation
Health checks are registered in the shared Microtec.Web.Core package and applied automatically to all services:
csharp
// Microtec.Web.Core / Extensions/HealthCheckExtensions.cs
public static IServiceCollection AddMicrotecHealthChecks(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddHealthChecks()
.AddSqlServer(
connectionString: configuration.GetConnectionString("DefaultConnection")!,
name: "database",
failureStatus: HealthStatus.Unhealthy,
tags: ["ready"])
.AddRedis(
configuration["RedisConfiguration:ConnectionString"]!,
name: "redis",
failureStatus: HealthStatus.Degraded,
tags: ["ready"])
.AddUrlGroup(
new Uri(configuration["Seq:ServerUrl"]! + "/api"),
name: "seq",
failureStatus: HealthStatus.Degraded,
tags: ["ready"]);
return services;
}
// Endpoint registration in Program.cs (via shared extension)
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false, // No checks — liveness is purely "process is up"
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});The liveness probe intentionally has Predicate = _ => false (no checks included) — it returns Healthy as long as the ASP.NET Core pipeline is processing requests, which is sufficient to confirm the process is alive.
Container App Probe Configuration
Container App probes are configured in the Bicep deployment module:
bicep
probes: [
{
type: 'Liveness'
httpGet: {
path: '/health/live'
port: 8080
scheme: 'HTTP'
}
initialDelaySeconds: 10
periodSeconds: 30
failureThreshold: 3
timeoutSeconds: 5
}
{
type: 'Readiness'
httpGet: {
path: '/health/ready'
port: 8080
scheme: 'HTTP'
}
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 5
timeoutSeconds: 10
}
{
type: 'Startup'
httpGet: {
path: '/health/ready'
port: 8080
scheme: 'HTTP'
}
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 30 // Allow up to 2.5 min for startup (EF migrations)
timeoutSeconds: 10
}
]Startup Probe for EF Migrations
The startup probe with failureThreshold: 30 and periodSeconds: 5 gives services up to 150 seconds to become ready. This accommodates EF Core migration execution on first deployment of a new schema version. Without a startup probe, the liveness probe would restart the container during migration.
Azure Front Door Health Probes
AFD probes use the aggregated /health endpoint (not readiness/liveness):
| Property | Value |
|---|---|
| Path | /health |
| Protocol | HTTPS |
| Method | GET |
| Interval | 30 seconds |
| Sample size | 4 samples |
| Successful threshold | 3 out of 4 |
AFD interprets HTTP 200 as healthy. Any other status code marks the origin as unhealthy and stops routing traffic to it.
Health Check UI
The HealthChecks.UI package runs as a sidecar dashboard on the Gateway service (dev and stage only):
| Environment | URL |
|---|---|
| dev | https://gateway.microtec-test.com/healthchecks-ui |
| stage | https://gateway.microtecstage.com/healthchecks-ui |
The dashboard polls all registered services every 30 seconds and shows a colour-coded grid. It is not deployed to preprod, uat, or production to reduce attack surface.
Restrict Health Check UI Access
The healthchecks-ui endpoint is restricted to the InternalUsers IP allowlist in the Gateway Ocelot configuration. It should never be accessible without authentication in any environment above dev.
Monitoring Integration
Health check results flow into Application Insights via:
- AFD availability alerts — AFD health probe failures create alerts (see alerting)
- OpenTelemetry — Health check probe call durations are captured as spans
- Seq — Health check pipeline logs at
Debuglevel in dev,Warningon unhealthy transitions in all environments