Add configurable host option to node renderer Fastify worker#2585
Add configurable host option to node renderer Fastify worker#2585
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a public Changes
Sequence Diagram(s)sequenceDiagram
participant Starter as Starter
participant Config as ConfigBuilder
participant Server as FastifyServer
participant Logger as Logger
Starter->>Config: getConfig()
Config-->>Starter: config { port, host, ... }
Starter->>Server: app.listen({ port, host })
Server-->>Logger: onListening(host, port)
Logger-->>Starter: log "listening on host:port"
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR adds a configurable Key changes:
Issues found:
Confidence Score: 4/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant Operator as Operator / Container Env
participant Config as configBuilder.ts
participant Worker as worker.ts
participant Fastify as Fastify Server
Operator->>Config: Set RENDERER_HOST (e.g. 0.0.0.0) or leave default
Config->>Config: defaultConfig.host = env.RENDERER_HOST || 'localhost'
Worker->>Config: buildConfig(userConfig)
Config-->>Worker: getConfig() → { port, host, ... }
Worker->>Fastify: app.listen({ port, host })
Fastify-->>Worker: Callback: listening on host:port
Worker->>Worker: log.info("Node renderer … listening on host:port!")
Note over Operator,Fastify: With host='0.0.0.0', external health checks<br/>(ALB, Docker) can reach the Fastify server
|
|
test comment please ignore |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is ON, but it could not run because Privacy Mode (Legacy) is turned on. To enable Bugbot Autofix, switch your privacy mode in the Cursor dashboard.
|
PR Review: The implementation is clean and correct — passing { port, host } to Fastify app.listen() is the right approach, and the default of localhost preserves existing behavior safely. Two issues: (1) Missing documentation update — configBuilder.ts line 23 has a comment reminding contributors to update js-configuration.md when config changes. The new host option backed by RENDERER_HOST was not documented there. (2) Missing CHANGELOG entry — this is a new user-facing configuration option that warrants an entry under Unreleased > Added in CHANGELOG.md. |
size-limit report 📦
|
PR ReviewThe implementation is clean and correct — passing Two issues to address: Missing documentation update
The Missing CHANGELOG entry This is a new user-facing configuration option. It warrants an entry under |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts`:
- Around line 150-151: env.RENDERER_HOST is used for the host value but
envValuesUsed() and therefore logSanitizedConfig() don't include RENDERER_HOST,
so startup diagnostics can't show if the bind address came from the environment;
update envValuesUsed() to include 'RENDERER_HOST' (and any matching key name
used in config) so logSanitizedConfig() will report whether host was set from
env.RENDERER_HOST or the default, referencing the host property and
env.RENDERER_HOST in
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts and
ensuring envValuesUsed() and logSanitizedConfig() are updated consistently.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 11678e41-ffee-4d1a-8b3f-a10cc3dd9ca9
📒 Files selected for processing (2)
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.tspackages/react-on-rails-pro-node-renderer/src/worker.ts
ReviewThe implementation is clean, correct, and addresses all the concerns raised by earlier reviewers. Specifically:
One remaining gap: no CHANGELOG entry This is a new user-facing configuration option for the Pro node renderer. Per the project's changelog guidelines, it should have an entry under - **Configurable renderer host binding (Pro)**: Added `host` config option (default: `'localhost'`, env: `RENDERER_HOST`) to the Node Renderer Fastify worker. Set to `0.0.0.0` in containerized environments (Docker, ECS with ALB) to allow external health checks. [PR 2585](https://github.com/shakacode/react_on_rails/pull/2585) by [justin808](https://github.com/justin808).Otherwise ready to merge once the CHANGELOG entry is added. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/pro/node-renderer/js-configuration.md`:
- Around line 15-16: Update the host guidance to include a security warning:
after the "host" (process.env.RENDERER_HOST || 'localhost') explanation, add a
short caution that binding to 0.0.0.0 exposes the renderer on all interfaces and
should only be used when protected by private networking, firewall/ALB rules, or
when authentication (the password config) is enabled; reference the "host" and
"password" settings so readers know which config keys to secure and recommend
alternatives (localhost or loopback) if those protections are not in place.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ec705949-d1e9-47ad-86f4-0c97c2b38ace
📒 Files selected for processing (2)
docs/pro/node-renderer/js-configuration.mdpackages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts
|
Overall this is a clean, well-scoped change. The default of 'localhost' preserves backward compatibility, the docs include a good security caution about 0.0.0.0 exposure, and the envValuesUsed() reporting is consistent with existing patterns. Issue: Log message shows configured host, not actual bound address In worker.ts, the app.listen callback form receives (err, address) as arguments where address is the fully-resolved string Fastify actually bound to. The current code ignores those callback params and logs the configured host:port values instead. This can be misleading when 'localhost' resolves to IPv6 (e.g. [::1]:3800). Using the address argument from the callback would make the log accurate regardless of how the OS resolves the hostname. Minor: No validation of the host string Invalid values (e.g. RENDERER_HOST=not-a-valid-host) will fail at runtime when Fastify tries to bind. Consistent with how port is handled, so acceptable — the failure is fast and visible at startup. Minor: envValuesUsed() falsy check !userConfig.host would also be truthy for an explicitly empty string host, causing the env variable to be reported as in use when it is not. Same pre-existing pattern as port, not a new issue introduced here. |
| app.listen({ port, host }, () => { | ||
| const workerName = worker ? `worker #${worker.id}` : 'master (single-process)'; | ||
| log.info(`Node renderer ${workerName} listening on port ${port}!`); | ||
| log.info(`Node renderer ${workerName} listening on ${host}:${port}!`); |
There was a problem hiding this comment.
The log uses the configured host value, but Fastify's callback receives (err, address) where address is the actual resolved address (e.g. http://[::1]:3800 when 'localhost' resolves to IPv6). Consider using the callback argument instead so the log always reflects the true bound address:
| log.info(`Node renderer ${workerName} listening on ${host}:${port}!`); | |
| log.info(`Node renderer ${workerName} listening on ${address}!`); |
(The callback signature is (err: Error | null, address: string) => void, so address is already available.)
There was a problem hiding this comment.
Addressed in commit 1b36cd3.
Updated the callback to use (err, address) — now logs the actual bound address from Fastify and exits with an error log if the listen fails.
|
Addressed in commit 4659dcf. Added a new Pro changelog entry under This addresses the missing changelog feedback noted in: |
Fastify defaults to listening on localhost (127.0.0.1), which breaks external health checks in containerized environments (Docker, ECS). Add a `host` config option (default: 'localhost', env: RENDERER_HOST) so users can set '0.0.0.0' for container deployments. Fixes shakacode/react_on_rails_pro#579 Co-Authored-By: Claude Opus 4.6 <[email protected]>
4659dcf to
a9fe5b0
Compare
| const { worker } = cluster; | ||
| if (workersCount === 0 || cluster.isWorker) { | ||
| app.listen({ port }, () => { | ||
| app.listen({ port, host }, () => { |
There was a problem hiding this comment.
The callback passed to app.listen ignores its arguments. In Fastify v5 the callback signature is (err: Error | null, address: string) => void. With the new host option, an invalid or unavailable bind address will now fail silently — no error is thrown or logged.
Consider either handling the error in the callback or switching to the promise form (which throws on failure and can be caught with try/catch):
| app.listen({ port, host }, () => { | |
| app.listen({ port, host }, (err, address) => { | |
| if (err) { | |
| log.error({ err }, `Node renderer failed to start`); | |
| process.exit(1); | |
| } | |
| const workerName = worker ? `worker #${worker.id}` : 'master (single-process)'; | |
| log.info(`Node renderer ${workerName} listening on ${address}!`); | |
| }); |
Using address from the callback is also more accurate than interpolating the configured host/port strings, since Fastify resolves localhost → 127.0.0.1 before binding and the callback receives the real bound address.
There was a problem hiding this comment.
Addressed in commit 1b36cd3.
Added error handling in the app.listen callback — on failure it now logs the error and exits. Also switched to using the callback's address argument for the startup log, which reflects the true bound address.
|
Overall this is a clean, minimal, well-motivated change. The security documentation in the docs page is a nice touch. What looks good
One issue worth addressing Left an inline comment on the |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/react-on-rails-pro-node-renderer/src/worker.ts (1)
465-467: Add a regression test for host-aware listen binding.Lines 465-467 correctly bind to
{ host, port }, but current test patterns likepackages/react-on-rails-pro-node-renderer/tests/streamErrorHang.test.tsusing only{ port: 0 }won’t catch future regressions on host propagation. Please add one integration test that asserts worker startup honors a non-default host.I can draft a minimal test case for this if you want.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/react-on-rails-pro-node-renderer/src/worker.ts` around lines 465 - 467, Add an integration test that verifies the server binds to a non-default host by exercising the same code path that calls app.listen({ host, port }) in worker.ts (the block that constructs workerName and calls app.listen). Create a new test (e.g., tests/hostBind.test.ts) that starts the renderer in-process or via the same startup helper used by other tests, passing a non-default host (for example '127.0.0.1' or 'localhost') and port: 0, then assert the running server's bound address equals the requested host (inspect the server instance returned by app.listen or the address reported by the running process). Use the existing test harness patterns from packages/react-on-rails-pro-node-renderer/tests/* to start/stop the worker cleanly and make sure the test fails if host propagation to app.listen is broken.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@packages/react-on-rails-pro-node-renderer/src/worker.ts`:
- Around line 465-467: Add an integration test that verifies the server binds to
a non-default host by exercising the same code path that calls app.listen({
host, port }) in worker.ts (the block that constructs workerName and calls
app.listen). Create a new test (e.g., tests/hostBind.test.ts) that starts the
renderer in-process or via the same startup helper used by other tests, passing
a non-default host (for example '127.0.0.1' or 'localhost') and port: 0, then
assert the running server's bound address equals the requested host (inspect the
server instance returned by app.listen or the address reported by the running
process). Use the existing test harness patterns from
packages/react-on-rails-pro-node-renderer/tests/* to start/stop the worker
cleanly and make sure the test fails if host propagation to app.listen is
broken.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 628136c7-6440-4e88-8a45-cd800f98c68b
📒 Files selected for processing (4)
CHANGELOG.mddocs/pro/node-renderer/js-configuration.mdpackages/react-on-rails-pro-node-renderer/src/shared/configBuilder.tspackages/react-on-rails-pro-node-renderer/src/worker.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- docs/pro/node-renderer/js-configuration.md
- packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts
Add error handling to the Fastify app.listen callback so invalid or unavailable bind addresses fail loudly instead of silently. Use the callback's `address` argument for the startup log, which reflects the true bound address (e.g. resolved IPv6) rather than the configured host/port strings. Co-Authored-By: Claude Opus 4.6 <[email protected]>
| app.listen({ port }, () => { | ||
| app.listen({ port, host }, (err, address) => { | ||
| if (err) { | ||
| log.error({ err }, `Node renderer failed to start`); |
There was a problem hiding this comment.
The error message doesn't include the address being bound to, which makes it harder to diagnose the root cause (e.g. invalid host value, port already in use, permission denied). Consider logging the attempted host/port:
| log.error({ err }, `Node renderer failed to start`); | |
| log.error({ err, host, port }, `Node renderer failed to start on ${host}:${port}`); |
There was a problem hiding this comment.
Addressed in commit 281993e. The error log now includes host and port in both the structured fields and the message string.
ReviewOverall this is a clean, well-scoped change. The default of Strengths
Minor issues
None of these block merging — items 1 and 3 are minor and item 2 is a nice-to-have. |
Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Overall this is a clean, well-scoped change. The default of localhost preserves current behaviour with zero risk to existing deployments; the security caution in the docs is clear and actionable. |
|
Overall this is a clean, well-scoped change. The default of localhost preserves current behaviour with zero risk to existing deployments; the security caution in the docs is clear and actionable. Improvement: Adding error handling to the app.listen callback is a genuine improvement -- previously a bind failure would silently pass through. Logging the resolved address from the callback (rather than configured strings) also correctly handles IPv6 address formatting. Minor -- envValuesUsed falsy check (see inline): The host: empty-string case is treated as not-set, consistent with the existing port pattern, but checking whether the host key exists in userConfig (same guard as supportModules/stubTimers) would be strictly correct. Minor -- no host validation: An invalid host string now fails loudly thanks to the new process.exit(1) path, but an upfront format check would give a clearer error message before Fastify even attempts to bind. Both are minor; the current implementation is functionally correct for all realistic inputs. |
| function envValuesUsed() { | ||
| return { | ||
| RENDERER_PORT: !userConfig.port && env.RENDERER_PORT, | ||
| RENDERER_HOST: !('host' in userConfig) && env.RENDERER_HOST, |
There was a problem hiding this comment.
The host check uses key-existence ('host' in userConfig) while every other field in this function uses a falsy check (e.g. !userConfig.port). This is actually more semantically correct — it means an explicit host: '' still suppresses the env-var report — but it creates a visible inconsistency and the test below only covers the empty-string edge case rather than the common case.
Consider either:
- Aligning this with the existing pattern:
!userConfig.host && env.RENDERER_HOST(accepts the same port-zero-style edge case the rest of the function already has), or - Keeping
inhere and adding a comment explaining why this check is intentionally different.
As-is, a reader maintaining the codebase may "fix" it to match the rest of the file and inadvertently change the behaviour.
| it('does not mark RENDERER_HOST as env-provided when host key exists in user config', () => { | ||
| process.env.RENDERER_HOST = '0.0.0.0'; | ||
|
|
||
| const envValues = envValuesUsedForRenderedConfig({ host: '' }); |
There was a problem hiding this comment.
The test passes { host: '' } — an empty string is an invalid bind address, so this is testing a somewhat artificial edge case. The intent is to verify that any explicit host key in userConfig (even a non-address value) suppresses env reporting. That intent would be clearer with a valid host value, and it would be even clearer if the test name reflected what is actually being asserted:
| const envValues = envValuesUsedForRenderedConfig({ host: '' }); | |
| const envValues = envValuesUsedForRenderedConfig({ host: '127.0.0.1' }); |
Also worth adding a third test for the fully-default path (no RENDERER_HOST env var, no host key in userConfig → effective host is 'localhost'), which isn't covered at all right now.
| fatal: jest.fn(), | ||
| }, | ||
| })); | ||
| const { buildConfig, logSanitizedConfig } = jest.requireActual('../src/shared/configBuilder'); |
There was a problem hiding this comment.
jest.requireActual loads the actual configBuilder implementation (bypassing any auto-mock of the module itself), but because jest.doMock was already registered for ../src/shared/log before this call, Jest resolves that dependency through the mock registry when configBuilder is freshly loaded — so the mock does take effect.
This pattern is established elsewhere in the test suite (e.g. licenseValidator.test.ts), so it's fine, but it's non-obvious. A brief inline comment here would help future readers understand why requireActual is used instead of require or requireMock.
|
Overall this is a clean, well-scoped change. The core implementation is solid: error handling in the app.listen callback is a genuine improvement over the previous fire-and-forget pattern, structured Pino log fields follow best practices, and using the resolved address from the callback for the success log correctly handles IPv6 address resolution. The security caution in the docs is appropriate. envValuesUsed() inconsistency (configBuilder.ts line 203): RENDERER_HOST uses the in operator (key existence) while all other fields use falsy checks like !userConfig.port. This is more semantically correct for host, but inconsistent with the surrounding code, and the accompanying test only exercises an empty-string edge case. See inline comment. Test coverage gaps: (1) The test passing host as an empty string uses an invalid bind address; a real value like 127.0.0.1 makes the intent clearer. (2) There is no test for the fully-default path (no RENDERER_HOST env var, no host key in userConfig -> defaults to localhost). (3) The jest.requireActual + jest.doMock pattern is correct and matches licenseValidator.test.ts, but a short comment on why requireActual is used would help future readers. No host validation (nice-to-have): an invalid host from an env var typo will reach app.listen without early feedback. The new error handler in worker.ts catches this at startup and exits, which is acceptable. A guard in buildConfig similar to the maxVMPoolSize check would give a faster and more descriptive error, but this is optional. None of these block merging - the feature is correct and the conservative default of localhost is safe. |
Summary
hostconfig option to the node renderer's Fastify listen call (default:'localhost', env:RENDERER_HOST)localhost/127.0.0.1, which blocks external health checks in containerized environments (Docker, ECS with ALB)0.0.0.0, so this restores configurabilityFixes shakacode/react_on_rails_pro#579
Test plan
pnpm run build)pnpm run lint)host: '0.0.0.0'in container environment to confirm health checks workhostset) still binds to localhost🤖 Generated with Claude Code
Note
Low Risk
Small change limited to server bind configuration; default behavior remains
localhost, with risk mainly around misconfiguration exposing the renderer on unintended interfaces.Overview
Adds a new
hostoption to the node renderer configuration (defaulting tolocalhostand overridable viaRENDERER_HOST).Updates the Fastify worker to pass
{ host, port }toapp.listen()and to include the bound address in startup logs, enabling scenarios like binding to0.0.0.0for external health checks.Written by Cursor Bugbot for commit 84b2b7b. Configure here.
Summary by CodeRabbit
New Features
Documentation