Skip to content

feat(service): Add /v1/health and /healthz endpoints for health checking#2169

Merged
tgasser-nv merged 6 commits into
developfrom
feat/service-health
Jul 14, 2026
Merged

feat(service): Add /v1/health and /healthz endpoints for health checking#2169
tgasser-nv merged 6 commits into
developfrom
feat/service-health

Conversation

@tgasser-nv

@tgasser-nv tgasser-nv commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Description

Adds /v1/health and /healthz endpoints for use by Kubernetes / Docker / clients to check the service is running. It's a shallow check, and doesn't check for downstream dependencies like the main-model or Guardrail models. It also doesn't check if a default Guardrails config was configured at server start-time, or if any are cached in the server llm_rails_instances variable.

Related Issue(s)

Test Plan

Pre-commit

$ uv run pre-commit run --all-files
check yaml...............................................................Passed
fix end of files.........................................................Passed
trim trailing whitespace.................................................Passed
ruff (legacy alias)......................................................Passed
ruff format..............................................................Passed
Insert license in comments...............................................Passed
zizmor...................................................................Passed
ty.......................................................................Passed

Unit-test

$ make test
...................................................................................................... [  1%]
......................................................................................s...ss.ss.ss.s.s [  3%]
....s..s.........................................................s.................................... [  5%]
...................................................................................................... [  7%]
..........................................................................................ss.......... [  9%]
...................................................................................................... [ 11%]
...s.................................................................................................. [ 13%]
...................................................................................................... [ 14%]
...................................................................................................... [ 16%]
...................................................................................................... [ 18%]
...................................................................................................... [ 20%]
...s...............................................................................s.................. [ 22%]
...................................................................................................... [ 24%]
.........................................................s............................................ [ 26%]
...................................................................................................... [ 27%]
...................................................................................................... [ 29%]
...................................................................................................... [ 31%]
.....................................................................ssss.sss......................sss [ 33%]
ss.sss..........................................................................................s..... [ 35%]
...................................................................................................... [ 37%]
...................................................................................................... [ 39%]
.........................................................s....................s.....s................. [ 41%]
...................................................................................................... [ 42%]
...................................................................................................... [ 44%]
...................................................................................................... [ 46%]
..........................s.ssssss.s...............ssss..ss....ss..s...........................sssssss [ 48%]
.s..s...sss.s......................................................................................... [ 50%]
...................................................................................................... [ 52%]
..........................................sss.s...s.s..ss............................................. [ 54%]
..........................................ss.......................................................... [ 55%]
.....................................s................................................................ [ 57%]
...................................................................................................... [ 59%]
.................................sssss...........sssssss.sssssss.ss.ss................................ [ 61%]
.................................................................s.................................... [ 63%]
...................................................................................................... [ 65%]
...................................................................................................... [ 67%]
.....................................................................................................s [ 68%]
ssss.....................................................................................sssssss...... [ 70%]
............s.....................................ss.................................................. [ 72%]
...................................................................................................... [ 74%]
......................................ss.................................s.s.......................... [ 76%]
...................................................................................................... [ 78%]
...................................................................................................... [ 80%]
.....................................................................................ss............... [ 82%]
.....s..........s....................sssssssssssss.................................................... [ 83%]
....................ssssss.......................s.................................................... [ 85%]
.s.ss................................................................................................. [ 87%]
.ss.......................................s........................................................... [ 89%]
...............s...........................s.......................................................... [ 91%]
..s............................ss........................s.....s...................................... [ 93%]
......................................................................................sssssssss.ssssss [ 95%]
ssss.................................................................................................. [ 96%]
.................................................................................s.................... [ 98%]
................................................................                                       [100%]

══════════════════════════════════════════════ inline-snapshot ═══════════════════════════════════════════════
INFO: inline-snapshot was disabled because you used xdist. This means that tests with snapshots will continue
to run, but snapshot(x) will only return x and inline-snapshot will not be able to fix snapshots or generate
reports.


===================================== 5292 passed, 178 skipped in 30.52s =====================================

Integration tests

Local server health checks

Server

$ uv run nemoguardrails server --config examples/configs --default-config-id nemoguards
INFO:     Started server process [20447]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:62546 - "GET /v1/health HTTP/1.1" 200 OK
INFO:     127.0.0.1:62550 - "GET /healthz HTTP/1.1" 200 OK

Client

$ curl -i http://localhost:8000/v1/health
HTTP/1.1 200 OK
date: Mon, 13 Jul 2026 19:41:13 GMT
server: uvicorn
content-length: 17
content-type: application/health+json

{"status":"pass"}%
$ curl -i http://localhost:8000/healthz
HTTP/1.1 200 OK
date: Mon, 13 Jul 2026 19:41:19 GMT
server: uvicorn
content-length: 17
content-type: application/health+json

{"status":"pass"}%

Docker health checks

Server

# Build image
$ docker build -t nemoguardrails:health-test .

# Inspect health check
$ docker inspect --format '{{json .Config.Healthcheck}}' nemoguardrails:health-test
{"Test":["CMD-SHELL","python -c \"import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/v1/health').status == 200 else 1)\""],"Interval":30000000000,"Timeout":5000000000,"StartPeriod":60000000000,"Retries":3}

# Run the container
$ docker run -d --name ng-health -p 8000:8000 nemoguardrails:health-test
89f354c836fe1f040d05503d93a78be2f6dc2b59da6e97523585e6e7748bc690

# Check if the service is up (waited for a couple of minutes)
$ docker logs -f ng-health
Warning: Input is not a terminal (fd=0).
INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     127.0.0.1:40768 - "GET /v1/health HTTP/1.1" 200 OK
INFO:     127.0.0.1:38594 - "GET /v1/health HTTP/1.1" 200 OK
INFO:     192.168.65.1:40653 - "GET /v1/health HTTP/1.1" 200 OK <- From the client curl command below
INFO:     192.168.65.1:18994 - "GET /healthz HTTP/1.1" 200 OK <- From the client curl command below
INFO:     127.0.0.1:49318 - "GET /v1/health HTTP/1.1" 200 OK
INFO:     127.0.0.1:49280 - "GET /v1/health HTTP/1.1" 200 OK
INFO:     127.0.0.1:52430 - "GET /v1/health HTTP/1.1" 200 OK
INFO:     127.0.0.1:59394 - "GET /v1/health HTTP/1.1" 200 OK

Client checking health endpoints inside Docker container

$ curl -i http://localhost:8000/v1/health
HTTP/1.1 200 OK
date: Mon, 13 Jul 2026 19:48:35 GMT
server: uvicorn
content-length: 17
content-type: application/health+json

{"status":"pass"}%
➜  ~ curl -i http://localhost:8000/healthz
HTTP/1.1 200 OK
date: Mon, 13 Jul 2026 19:48:39 GMT
server: uvicorn
content-length: 17
content-type: application/health+json

{"status":"pass"}%

Docker inspect health

$ docker inspect \
  --format='{{.State.Health.Status}}' \
  ng-health

healthy

Docs build check

$ make docs-fern-strict
FERN_VERSION=$(node -p "require('./fern/fern.config.json').version") && cd fern && npx --yes "fern-api@${FERN_VERSION}" docs md generate --library guardrails-python-sdk
Library 'guardrails-python-sdk': generated 290 pages at /Users/tgasser/projects/nemo_guardrails_worktree/feat/service-health/docs/_static/python-sdk-reference
✓ Generated library documentation for 1 libraries
npm notice New minor version of npm available! 11.12.1 -> 11.18.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.18.0
npm notice To update run: npm install -g npm@11.18.0
node scripts/normalize-fern-sdk-reference.mjs
Moved 0 generated package overview pages to index.mdx.
Removed 0 duplicate package overview pages with existing index.mdx.
FERN_VERSION=$(node -p "require('./fern/fern.config.json').version") && cd fern && npx --yes "fern-api@${FERN_VERSION}" check
All checks passed

AI Assistance

  • No AI tools were used.
  • AI tools were used; a human reviewed and can explain every change (tool: ___).

Checklist

  • I've read the CONTRIBUTING guidelines.
  • This PR links to a triaged issue assigned to me.
  • My PR title follows the project commit convention.
  • I've updated the documentation if applicable.
  • I've added tests if applicable.
  • I've noted any verification beyond CI and any checks I couldn't run.
  • I did not update generated changelog files manually.
  • I addressed all CodeRabbit, Greptile, and other review comments, or replied with why no change is needed.
  • @mentions of the person or team responsible for reviewing proposed changes.

Summary by CodeRabbit

  • New Features

    • Added liveness health-check endpoints at /v1/health and /healthz.
    • Health checks return a standardized success response when the server process is available.
    • Containers now report their health automatically based on the server health endpoint.
  • Documentation

    • Documented health-check endpoints, response formats, and behavior, including prefixed deployments.

@github-actions github-actions Bot added size: M status: needs triage New issues that have not yet been reviewed or categorized. labels Jul 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@tgasser-nv tgasser-nv changed the title feat(service): Add /v1/health endpoint for health checking feat(service): Add /v1/health and /v1/healthz endpoints for health checking Jul 13, 2026
@tgasser-nv tgasser-nv changed the title feat(service): Add /v1/health and /v1/healthz endpoints for health checking feat(service): Add /v1/health and /healthz endpoints for health checking Jul 13, 2026
@tgasser-nv
tgasser-nv marked this pull request as ready for review July 13, 2026 19:52
@tgasser-nv tgasser-nv self-assigned this Jul 13, 2026
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@greptile-apps Review this PR

@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Review this PR

@tgasser-nv tgasser-nv added status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile). and removed status: needs triage New issues that have not yet been reviewed or categorized. labels Jul 13, 2026
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
gh pr view 2169 --repo NVIDIA-NeMo/Guardrails --json title,body,files,additions,deletions
echo "-----DIFF-----"
gh pr diff 2169 --repo NVIDIA-NeMo/Guardrails

Length of output: 18473


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,60p' nemoguardrails/server/api.py
echo "---"
sed -n '200,300p' nemoguardrails/server/api.py

Length of output: 5363


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "api_key|Authorization|middleware|dependencies=" nemoguardrails/server/api.py | head -60

Length of output: 404


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "prefix" nemoguardrails/server/api.py nemoguardrails/cli/*.py 2>/dev/null | head -40

Length of output: 980


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "`@app`\.(get|mount|include_router)|StaticFiles" nemoguardrails/server/api.py

Length of output: 364


@tgasser-nv Reviewed the PR — this is a clean, minimal implementation of the health-check endpoints. Here's my assessment:

What looks good:

  • Stacking two @app.get decorators (/v1/health and /healthz) on the single health() function in nemoguardrails/server/api.py (Line 256) is valid FastAPI/Starlette usage and avoids code duplication.
  • No auth/API-key middleware exists in api.py, so these endpoints are open by default — consistent with other unauthenticated endpoints like /v1/rails/configs.
  • I verified prefix handling: nemoguardrails/cli/__init__.py (Line 205) does server_app.mount(prefix, api.app), so the docs claim about <prefix>/v1/health and <prefix>/healthz when --prefix is used is accurate.
  • No catch-all route (e.g., /{path}) exists elsewhere in api.py that could shadow or intercept /healthz — only the literal / route (used for chat-UI redirect) is defined, so there's no routing conflict.
  • The Dockerfile HEALTHCHECK using /v1/health via urllib.request is a sensible, dependency-free choice (avoids requiring curl in the image).
  • Test coverage in tests/server/test_health.py is solid: checks 200/pass status, exact media type, 405 on POST, behavior with no cached rails/config, and /healthz parity with /v1/health.

Minor observations (non-blocking):

  • The health() function's docstring/summary doesn't set an explicit tags= for OpenAPI grouping like some other endpoints might benefit from, but this is stylistic and not required.
  • Since this is intentionally a shallow liveness check (as stated in the PR description), it's good that it doesn't touch llm_rails_instances or model/config state — this keeps the check fast and dependency-free, consistent with typical k8s liveness probe semantics.

Overall, the implementation matches the stated objectives well and the test/integration evidence in the PR description (local server + Docker) is thorough. No blocking issues found.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@greptile-apps

greptile-apps Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds shallow health checks for the Guardrails server. The main changes are:

  • New /v1/health and /healthz endpoints.
  • Docker image healthcheck using a configurable probe URL.
  • Documentation for server and container health checks.
  • OpenAPI and docs navigation entries for the health endpoints.
  • Tests covering response status, media type, method handling, and the alias route.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
Dockerfile Adds a configurable Docker healthcheck URL and probes it without proxy handling.
nemoguardrails/server/api.py Adds shallow health routes that return application/health+json with a pass status.
docs/deployment/using-docker.mdx Documents when the container healthcheck applies and how to override its URL.
docs/run-rails/using-fastapi-server/run-guardrails-server.mdx Documents the server health endpoints and their behavior with path prefixes.
fern/openapi.yml Adds OpenAPI entries for both health endpoints.
tests/server/test_health.py Adds tests for the new health endpoint contract and alias.

Reviews (7): Last reviewed commit: "Bypass proxies in requests library healt..." | Re-trigger Greptile

Comment thread Dockerfile Outdated
@greptile-apps

This comment was marked as outdated.

Comment thread Dockerfile Outdated
@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds /v1/health and /healthz liveness endpoints, tests their response contracts, config independence, and method restrictions, documents their usage, and configures Docker health probing.

Changes

Health Check Flow

Layer / File(s) Summary
Liveness endpoint implementation
nemoguardrails/server/api.py
Adds shared health handling for /v1/health and /healthz, returning HTTP 200 with the application/health+json content type and {"status": "pass"} payload.
Health check validation and integration
tests/server/test_health.py, Dockerfile, docs/run-rails/using-fastapi-server/run-guardrails-server.mdx
Tests endpoint contracts and config-independent behavior, configures Docker probing, and documents endpoint responses and prefix handling.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: pouyanpi, drazvan, xiaobo8204, christinaexyou, miyoungc

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Results For Major Changes ✅ Passed PR adds new health endpoints and docs, and the description documents pre-commit, unit, and integration test results.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change by describing the new /v1/health and /healthz health-check endpoints.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/service-health

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/run-rails/using-fastapi-server/run-guardrails-server.mdx`:
- Around line 229-245: Add both documented health endpoints, GET /v1/health and
GET /healthz, to the “Reference → Guardrails API Server” section of
docs/index.yml alongside the existing GET / entry, preserving the current API
reference structure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 52ecf7d1-d55a-4860-b036-9260a0c64bd4

📥 Commits

Reviewing files that changed from the base of the PR and between c077dbe and fc97d11.

📒 Files selected for processing (4)
  • Dockerfile
  • docs/run-rails/using-fastapi-server/run-guardrails-server.mdx
  • nemoguardrails/server/api.py
  • tests/server/test_health.py

Comment thread docs/run-rails/using-fastapi-server/run-guardrails-server.mdx
Comment thread Dockerfile Outdated
@tgasser-nv
tgasser-nv requested review from Pouyanpi and cparisien July 13, 2026 20:31

@Pouyanpi Pouyanpi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @tgasser-nv, for adding this!

One thing I noticed: this works well for a server-only image, but the same image also supports commands like nemoguardrails chat (see docs):

docker run --rm -it \
  --name guardrails-chat \
  -e OPENAI_API_KEY="$OPENAI_API_KEY" \
  nemoguardrails \
  chat --config=/config --verbose

Since chat doesn’t start an HTTP server, the health check eventually marks the container as unhealthy, even though the CLI is working:

docker inspect \
  --format='{{.State.Health.Status}}' \
  guardrails-chat

Initially it reports starting; if chat remains open long enough, it becomes unhealthy.

You can inspect individual probe attempts immediately:

  docker inspect \
    --format='{{range .State.Health.Log}}
    {{println .End .ExitCode .Output}}{{end}}' \
    guardrails-chat

because chat has no http listener, the probe should eventually fail with a connection refused error and the status should become: unhealthy

Could we skip the HTTP probe when the container isn’t running in server mode? Another option would be to document --no-healthcheck for non-server commands. Or whatever you think is a better option would be great.

Also urllib.request.urlopen() uses HTTP_PROXY/ http_proxy. It might be worth disabling proxies for this localhost request so a healthy server isn’t marked unhealthy when NO_PROXY isn’t set.

It would be nice to cover both cases in the Docker workflow as well.

@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

Thanks, @tgasser-nv, for adding this!

One thing I noticed: this works well for a server-only image, but the same image also supports commands like nemoguardrails chat (see docs)

I added instructions in the docs to use --no-healthcheck for non-server Docker apps.

Also urllib.request.urlopen() uses HTTP_PROXY/ http_proxy. It might be worth disabling proxies for this localhost request so a healthy server isn’t marked unhealthy when NO_PROXY isn’t set.

I bypassed proxies in the health checking code.

@tgasser-nv
tgasser-nv merged commit 69d3c2a into develop Jul 14, 2026
19 checks passed
@tgasser-nv
tgasser-nv deleted the feat/service-health branch July 14, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: M status: triaged Triaged by a maintainer; eligible for automated review (CodeRabbit/Greptile).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants