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 Check Endpoint Architecture
Load Balancer / K8s
/health/live — Always 200
/health/startup — DB migration + seed
/health/ready — DB + Redis + Modules
/health — All checks (auth)
/health/deep — Full diagnostic (auth)

Health Endpoints

EndpointAuthTag FilterChecks IncludedUse Case
/health/liveNonePredicate = false (no checks)None — always 200 if process is aliveK8s liveness probe — if this fails, K8s kills and restarts the pod
/health/startupNonestartupDatabase (migrated?), Startup seed verificationK8s startup probe — has the app finished initializing? Allows up to 5 minutes
/health/readyNonereadyDatabase, Redis, Module health (in-process or HTTP)K8s readiness probe — if this fails, K8s removes pod from load balancer endpoints
/healthBearer JWTAll (Predicate = true)Database, Redis, SMTP, Storage, Startup, ModulesAdmin health dashboard — detailed JSON response with durations and exceptions
/health/deepBearer JWTdeepDatabase, Redis, SMTP, Storage, ModulesFull 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).

CheckTagsFailure StatusWhat It ValidatesFailure Behavior
DatabaseHealthCheckdb, ready, startup, deepUnhealthySQL connectivity via SELECT 1 query (supports SqlServer, Oracle, PostgreSql)Returns Unhealthy — no traffic is routed to this instance
RedisHealthCheckcache, ready, deepDegradedRedis PING/PONG roundtrip (skips if Redis is not configured)Returns Degraded — app falls back to in-memory cache automatically
SmtpHealthChecksmtp, deepDegradedTCP socket connection to configured SMTP server and portReturns Degraded — email sending disabled but app continues
StorageHealthCheckstorage, deepDegradedBlob storage provider write/read test file cycleReturns Degraded — file uploads disabled but app continues
StartupHealthCheckstartupUnhealthyVerifies EF migrations are applied + seed data (SuperAdmin, default roles) existsReturns Unhealthy during startup, Healthy once migration + seed complete
ModuleHealthCheckmodules, ready, deepDegradedMonolith: checks in-process module registration. Microservices: HTTP call to each service health endpointReturns 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.

HealthCheckExtensions.cs — Check RegistrationC#
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.

Kubernetes Deployment — Health ProbesYAML
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: 3

Docker 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.

docker-compose.yml — Health Check ConfigurationYAML
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: 3

Response 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.

Minimal Response — /health/live, /health/startup, /health/readyJSON
{
  "status": "Healthy",
  "duration": 23.4567,
  "checks": [
    {
      "name": "database",
      "status": "Healthy",
      "description": null
    },
    {
      "name": "redis",
      "status": "Healthy",
      "description": "Redis not configured (using in-memory cache)"
    }
  ]
}
Detailed Response — /health, /health/deep (authenticated)JSON
{
  "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

EnvironmentRecommended EndpointsConfiguration Notes
Development (local)/health/live, /health/readyRun without Redis/SMTP — checks return Degraded but app works. Use in-memory cache fallback.
Docker Compose/health/live (healthcheck), /health/readySet start_period: 60s to allow migrations. Use depends_on with service_healthy condition.
Kubernetes/health/live, /health/startup, /health/readyUse all 3 probes. startupProbe with 30 × 10s = 5 min for migration. readinessProbe gates traffic.
IIS (Windows Server)/health, /health/deepNo K8s probes needed. Use /health for load balancer health check and /health/deep for SCOM/monitoring.
Production (any)All 5 endpointsWire /health and /health/deep to Grafana for monitoring dashboards. Set up alerts on Unhealthy status.