Skip to main content

Documentation Index

Fetch the complete documentation index at: https://tally.wharflab.com/llms.txt

Use this file to discover all available pages before exploring further.

Rails 7.1+ ships a /up health endpoint by default. The Ruby stdlib’s Net::HTTP is already in the image — no extra apt-get install curl needed.
PropertyValue
SeverityInfo
CategoryCorrectness
DefaultEnabled (advisory)
Auto-fixFixSuggestion (no-edit)

Description

Rails 7.1 added Rails::HealthController mounted at /up by default. It returns 200 if the app booted cleanly, 500 otherwise — exactly the right shape for a Docker HEALTHCHECK. Almost no Rails Dockerfile uses it, and the few that do reach for curl, which forces an extra package install on ruby:*-slim and ruby:*-alpine bases (where curl is not present by default). This rule has two flavors:
  1. Missing HEALTHCHECK on a Rails 7.1+ runtime stage — recommend adding the canonical Ruby-native one.
  2. HEALTHCHECK present but uses curl or wget — suggest replacing with the Ruby stdlib equivalent. Especially when the same Dockerfile installs curl only because the healthcheck needs it (the corpus shows this exact pattern).
The corpus shows:
  • 6 of 196 use HEALTHCHECK at all.
  • 2 of 196 target /up.
  • Both /up healthchecks use curl against a ruby:*-slim base — both then need apt-get install ... curl ... upstream of that line.
  • 0 of 196 use a Ruby-native healthcheck.
This rule fires when:
  • The final stage is a Rails web-server runtime (Ruby base + ENTRYPOINT/CMD references rails/puma/unicorn/thrust/rackup/falcon/thin/passenger/iodine).
  • Variant 1: no HEALTHCHECK at all.
  • Variant 2: HEALTHCHECK present and the command starts with curl or wget.
The rule deliberately ignores worker stages (sidekiq, resque, …) — they don’t expose an HTTP endpoint, so /up would not respond there. HEALTHCHECK NONE (the explicit “I have an external orchestrator” signal) is recognized as a deliberate opt-out and suppresses the rule.

Examples

Before

FROM ruby:3.3-slim
COPY . .
CMD ["bin/rails", "server"]
Or with curl:
FROM ruby:3.3-slim
RUN apt-get update && apt-get install -y curl
COPY . .
HEALTHCHECK CMD curl -fsS http://127.0.0.1:3000/up || exit 1
CMD ["bin/rails", "server"]

After

The Ruby-native form uses the stdlib Net::HTTP (already in the image), targets /up (the Rails default), and uses the JSON exec form so the command runs as execve rather than under /bin/sh -c:
FROM ruby:3.3-slim
COPY . .
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
  CMD ["ruby", "-rnet/http", "-e", "exit Net::HTTP.get_response(URI('http://127.0.0.1:3000/up')).is_a?(Net::HTTPSuccess) ? 0 : 1"]
CMD ["bin/rails", "server"]
Why this exact shape:
  • ruby -rnet/http -e keeps the entire HTTP client in the image’s existing Ruby interpreter — no curl, no wget, no extra layer.
  • Net::HTTP.get_response(URI('...')) returns a Net::HTTPResponse whose subclass implements Net::HTTPSuccess for any 2xx — so the check succeeds for /up’s 200 OK and correctly fails on 500 (which is what Rails::HealthController returns when the boot fails).
  • Using 127.0.0.1 rather than localhost avoids one DNS lookup per probe and is robust against IPv6-only localhost weirdness inside containers.
  • The JSON exec form (["ruby", ...]) means Docker runs the command directly via execve rather than spawning /bin/sh -c. This matters more for HEALTHCHECK than CMD because healthchecks run on every interval — the per-invocation shell startup adds up.

Auto-fix

FixSuggestion (no-edit). Description names the canonical Ruby-native form. Variant 2 (curl/wget present) also notes that the user can drop the apt-get install curl line if it was added solely for the healthcheck.

References