Health Checks & K8s Probes
Enterprise health endpoints for Kubernetes liveness, readiness, and startup probes with 6 individual checks covering Database, Redis, SMTP, Storage, Startup, and Module health.
SCRIPE provides 5 enterprise-grade health endpoints designed for Kubernetes orchestration, load balancer integration, and operations monitoring. Each endpoint validates specific infrastructure dependencies and returns structured JSON responses. The system uses a tag-based architecture where each check is tagged (db, cache, smtp, storage, startup, modules, ready, deep) and endpoints filter by tags to include only relevant checks.
Health Endpoint Architecture
Health Endpoints
| Endpoint | Auth | Tag Filter | Checks Included | Use Case |
|---|---|---|---|---|
| /health/live | None | Predicate = false (no checks) | None — always 200 if process is alive | K8s liveness probe — if this fails, K8s kills and restarts the pod |
| /health/startup | None | startup | Database (migrated?), Startup seed verification | K8s startup probe — has the app finished initializing? Allows up to 5 minutes |
| /health/ready | None | ready | Database, Redis, Module health (in-process or HTTP) | K8s readiness probe — if this fails, K8s removes pod from load balancer endpoints |
| /health | Bearer JWT | All (Predicate = true) | Database, Redis, SMTP, Storage, Startup, Modules | Admin health dashboard — detailed JSON response with durations and exceptions |
| /health/deep | Bearer JWT | deep | Database, Redis, SMTP, Storage, Modules | Full diagnostic for operations teams — includes exception details and data payloads |
Individual Health Checks
Each health check validates a specific infrastructure dependency. Checks run in parallel for minimal latency. Failed checks return detailed error information without leaking sensitive connection strings. The failure status is configurable per check — Database and Startup failures return Unhealthy (kills the pod), while Redis, SMTP, and Storage return Degraded (app continues with fallbacks).
| Check | Tags | Failure Status | What It Validates | Failure Behavior |
|---|---|---|---|---|
| DatabaseHealthCheck | db, ready, startup, deep | Unhealthy | SQL connectivity via SELECT 1 query (supports SqlServer, Oracle, PostgreSql) | Returns Unhealthy — no traffic is routed to this instance |
| RedisHealthCheck | cache, ready, deep | Degraded | Redis PING/PONG roundtrip (skips if Redis is not configured) | Returns Degraded — app falls back to in-memory cache automatically |
| SmtpHealthCheck | smtp, deep | Degraded | TCP socket connection to configured SMTP server and port | Returns Degraded — email sending disabled but app continues |
| StorageHealthCheck | storage, deep | Degraded | Blob storage provider write/read test file cycle | Returns Degraded — file uploads disabled but app continues |
| StartupHealthCheck | startup | Unhealthy | Verifies EF migrations are applied + seed data (SuperAdmin, default roles) exists | Returns Unhealthy during startup, Healthy once migration + seed complete |
| ModuleHealthCheck | modules, ready, deep | Degraded | Monolith: checks in-process module registration. Microservices: HTTP call to each service health endpoint | Returns Degraded if one or more modules are unreachable |
Health Check Registration
Health checks are registered centrally in HealthCheckExtensions.cs with explicit tags and failure statuses. Tags determine which endpoint includes each check. The tag-based design means adding a new check is a single line change — register it with the appropriate tags and it automatically appears in the correct endpoints.
services.AddHealthChecks()
// Database (primary dependency — Unhealthy = kill pod)
.AddCheck<DatabaseHealthCheck>("database",
HealthStatus.Unhealthy,
tags: ["db", "ready", "startup", "deep"])
// Redis (conditional — Degraded = keep pod, use fallback)
.AddCheck<RedisHealthCheck>("redis",
HealthStatus.Degraded,
tags: ["cache", "ready", "deep"])
// SMTP (email sending)
.AddCheck<SmtpHealthCheck>("smtp",
HealthStatus.Degraded,
tags: ["smtp", "deep"])
// Blob Storage (file uploads)
.AddCheck<StorageHealthCheck>("storage",
HealthStatus.Degraded,
tags: ["storage", "deep"])
// Startup (DB migrated + seed data present)
.AddCheck<StartupHealthCheck>("startup",
HealthStatus.Unhealthy,
tags: ["startup"])
// Module Health (monolith: in-process / microservice: HTTP)
.AddCheck<ModuleHealthCheck>("modules",
HealthStatus.Degraded,
tags: ["modules", "ready", "deep"]);Kubernetes Probe Configuration
SCRIPE's health endpoints map directly to Kubernetes probe types. The startup probe allows up to 5 minutes (30 failures × 10s interval) for database migration on first deployment. The readiness probe gates traffic routing — if Database or Redis fails, K8s removes the pod from load balancer endpoints. The liveness probe detects hung processes with no dependency checks.
apiVersion: apps/v1
kind: Deployment
metadata:
name: scripe-api
spec:
replicas: 3
template:
spec:
containers:
- name: scripe
image: scripe-api:latest
ports:
- containerPort: 5035
# STARTUP: Allow up to 5 minutes for DB migration on first deploy
startupProbe:
httpGet:
path: /health/startup
port: 5035
failureThreshold: 30 # 30 × 10s = 5 minutes max
periodSeconds: 10
# LIVENESS: Is the process alive? If not, K8s kills the pod
livenessProbe:
httpGet:
path: /health/live
port: 5035
initialDelaySeconds: 5
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 3
# READINESS: Can this pod handle traffic? If not, remove from LB
readinessProbe:
httpGet:
path: /health/ready
port: 5035
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3Docker Compose Health Check
For Docker Compose deployments, configure health checks on the service definition. Use /health/live for basic liveness and /health/ready for readiness. Set start_period to allow time for database migrations before health checks begin. In microservice mode, each module service gets its own health check.
services:
scripe-api:
image: scripe-api:latest
ports:
- "5035:5035"
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:5035/health/live || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s # Wait for DB migration before checking
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
# Monolith mode: single service with all modules
# Microservice mode: one service per module with MODULE_NAME env var
scripe-identity:
image: scripe-api:latest
environment:
- MODULE_NAME=Identity
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:5035/health/ready || exit 1"]
interval: 30s
timeout: 5s
retries: 3Response Format
SCRIPE supports two response formats depending on the endpoint. Public probe endpoints (/health/live, /health/startup, /health/ready) return a minimal JSON with status, duration, and check names. Authenticated endpoints (/health, /health/deep) return a detailed response including per-check durations, tags, data payloads, and exception details for operations teams.
{
"status": "Healthy",
"duration": 23.4567,
"checks": [
{
"name": "database",
"status": "Healthy",
"description": null
},
{
"name": "redis",
"status": "Healthy",
"description": "Redis not configured (using in-memory cache)"
}
]
}{
"status": "Healthy",
"duration_ms": 45.789,
"timestamp": "2026-04-06T21:00:00Z",
"checks": [
{
"name": "database",
"status": "Healthy",
"description": "SQL Server connected",
"duration_ms": 12.34,
"tags": ["db", "ready", "startup", "deep"],
"data": null,
"exception": null
},
{
"name": "smtp",
"status": "Degraded",
"description": "SMTP server unreachable",
"duration_ms": 5000.0,
"tags": ["smtp", "deep"],
"data": { "host": "smtp.example.com", "port": "587" },
"exception": {
"type": "SocketException",
"message": "Connection refused"
}
}
]
}Environment-Specific Guide
| Environment | Recommended Endpoints | Configuration Notes |
|---|---|---|
| Development (local) | /health/live, /health/ready | Run without Redis/SMTP — checks return Degraded but app works. Use in-memory cache fallback. |
| Docker Compose | /health/live (healthcheck), /health/ready | Set start_period: 60s to allow migrations. Use depends_on with service_healthy condition. |
| Kubernetes | /health/live, /health/startup, /health/ready | Use all 3 probes. startupProbe with 30 × 10s = 5 min for migration. readinessProbe gates traffic. |
| IIS (Windows Server) | /health, /health/deep | No K8s probes needed. Use /health for load balancer health check and /health/deep for SCOM/monitoring. |
| Production (any) | All 5 endpoints | Wire /health and /health/deep to Grafana for monitoring dashboards. Set up alerts on Unhealthy status. |