module HealthCheckable
extend ActiveSupport::Concern
CheckResult = Struct.new(:name, :ok, :message, keyword_init: true)
private
def run_checks
[check_database, check_redis]
end
def check_database
safe_check("database") do
ActiveRecord::Base.connection.execute("SELECT 1")
true
end
end
def check_redis
safe_check("redis") do
Redis.current.ping == "PONG"
end
end
def safe_check(name)
ok = yield
CheckResult.new(name: name, ok: ok, message: ok ? "ok" : "unexpected response")
rescue StandardError => e
CheckResult.new(name: name, ok: false, message: e.message)
end
end
class HealthController < ApplicationController
include HealthCheckable
skip_before_action :authenticate_user!, raise: false
skip_forgery_protection
def liveness
render json: { status: "ok" }, status: :ok
end
def readiness
results = run_checks
healthy = results.all?(&:ok)
render(
json: {
status: healthy ? "ok" : "degraded",
checks: results.map { |r| { name: r.name, status: r.ok ? "up" : "down", message: r.message } }
},
status: healthy ? :ok : :service_unavailable
)
end
end
Rails.application.routes.draw do
namespace :health, controller: :health do
get :up, action: :liveness
get :ready, action: :readiness
end
# convenience aliases matching common probe paths
get "/healthz", to: "health#liveness"
get "/readyz", to: "health#readiness"
end
A health-check endpoint is the contract between a Rails app and whatever is watching it — a load balancer, a Kubernetes kubelet, or an uptime monitor. The tricky part is distinguishing liveness (the process is up and answering) from readiness (the process can actually serve traffic because its dependencies are reachable). This snippet separates those concerns and keeps the probe logic reusable.
The HealthCheckable concern centralizes the individual checks so any controller can mix them in. check_database runs a trivial SELECT 1 through ActiveRecord::Base.connection.execute, which is the cheapest way to confirm the connection pool can hand out a live, authenticated connection rather than just confirming the config parses. check_redis issues a ping and treats the string reply as truth. Each check is wrapped in safe_check, which rescues StandardError, records the failure message, and never lets one dependency's exception blow up the whole probe — an important property, because a health endpoint that 500s is useless to the very systems polling it.
In HealthController, liveness returns 200 unconditionally and does no I/O; it answers the question "should this pod be restarted?" and must stay fast and dependency-free. readiness runs the full check set via run_checks and returns 503 Service Unavailable when any dependency is down, which is exactly the signal a load balancer needs to stop routing traffic without killing the process. The response body is JSON listing per-check status, so an operator can see which dependency failed. skip_before_action :authenticate_user! and skip_forgery_protection keep probes unauthenticated, since orchestrators cannot present credentials.
The routes tab wires clean paths under a health namespace, mapping up to liveness and ready to readiness — mirroring common Kubernetes probe conventions.
A key trade-off: readiness checks add latency and load, so they should be lightweight and never cascade (avoid probing a dependency's dependencies). A subtle pitfall is running readiness on a path that itself requires the database via session lookups or auth middleware; skipping those callbacks avoids a probe that fails for the wrong reason. This pattern is worth reaching for the moment an app runs behind any orchestrator that makes traffic and restart decisions based on HTTP status.
Related snips
package api
import (
"net/http"
"runtime/debug"
)
Expose build metadata for debugging deploys
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
package dbutil
import (
"context"
"github.com/jackc/pgconn"
Retry Postgres serialization failures with bounded attempts
Share this code
Here's the card — post it anywhere.