Skip to main content

Using Process Managers with React on Rails

React on Rails requires running multiple processes simultaneously during development:

  • Rails server
  • Webpack dev server (client bundle)
  • Webpack watcher (server bundle)

Running Your Development Server

React on Rails includes bin/dev which automatically uses Overmind or Foreman:

./bin/dev

This script will:

  1. Check database connectivity (unless disabled)
  2. Check required external services (if .dev-services.yml exists)
  3. Run Shakapacker's precompile_hook once (if configured in config/shakapacker.yml)
  4. Set SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true to prevent duplicate execution
  5. Try to use Overmind (if installed)
  6. Fall back to Foreman (if installed)
  7. Show installation instructions if neither is found

Precompile Hook Integration

If you have configured a precompile_hook in config/shakapacker.yml, bin/dev will automatically:

  • Execute the hook once before starting development processes
  • Set the SHAKAPACKER_SKIP_PRECOMPILE_HOOK environment variable
  • Pass this environment variable to all spawned processes (Rails, webpack, etc.)
  • Prevent webpack processes from re-running the hook independently

Note: Shakapacker 9.4.0+ supports SHAKAPACKER_SKIP_PRECOMPILE_HOOK natively. For Shakapacker 9.0-9.3, script-based hooks remain reliable when the script includes a self-guard:

exit 0 if ENV["SHAKAPACKER_SKIP_PRECOMPILE_HOOK"] == "true"

bin/dev warns only when your hook cannot safely self-guard (for example, a direct command hook, or a script hook missing the guard line).

This eliminates the need for manual coordination in your Procfile.dev. For example:

Before (manual coordination with sleep hacks):

# Procfile.dev
wp-server: sleep 15 && bundle exec rake react_on_rails:locale && bin/shakapacker --watch

After (automatic coordination via bin/dev):

# Procfile.dev
wp-server: bin/shakapacker --watch
# config/shakapacker.yml
default: &default
precompile_hook: 'bundle exec rake react_on_rails:locale'
tip

For HMR with SSR setups (two webpack processes), use a script-based hook instead of a direct command. Script-based hooks can include a self-guard that prevents duplicate execution regardless of Shakapacker version. See the i18n documentation for an example.

Upgrading Existing Apps

If your app currently uses a direct command hook, such as:

precompile_hook: 'bundle exec rake react_on_rails:locale'

migrate to a script-based hook:

  1. Create bin/shakapacker-precompile-hook:

    #!/usr/bin/env ruby
    # frozen_string_literal: true
    exit 0 if ENV["SHAKAPACKER_SKIP_PRECOMPILE_HOOK"] == "true"
    system("bundle", "exec", "rake", "react_on_rails:locale", exception: true)
  2. Make it executable:

    chmod +x bin/shakapacker-precompile-hook
  3. Update config/shakapacker.yml:

    default: &default
    precompile_hook: 'bin/shakapacker-precompile-hook'

This upgrade path works for both Shakapacker 9.0-9.3 and 9.4.0+.

See the i18n documentation for more details on configuring the precompile hook.

Alternative: Extensible Precompile Pattern

For projects with custom build requirements (ReScript, TypeScript compilation, multiple precompile tasks), consider handling precompile tasks directly in bin/dev instead of using the precompile_hook mechanism.

This approach provides:

  • Single place to manage all precompile tasks
  • Direct Ruby API calls (faster, better version manager compatibility)
  • Clean Procfiles without embedded precompile logic

See the Extensible Precompile Pattern guide for full details.

Service Dependency Checking

bin/dev can automatically verify that required external services (like Redis, PostgreSQL, Elasticsearch) are running before starting your development server. This prevents cryptic error messages and provides clear instructions on how to start missing services.

Configuration

Create a .dev-services.yml file in your project root:

services:
redis:
check_command: 'redis-cli ping'
expected_output: 'PONG'
start_command: 'redis-server'
install_hint: 'brew install redis (macOS) or apt-get install redis-server (Linux)'
description: 'Redis (for caching and background jobs)'

postgresql:
check_command: 'pg_isready'
expected_output: 'accepting connections'
start_command: 'pg_ctl -D /usr/local/var/postgres start'
install_hint: 'brew install postgresql (macOS)'
description: 'PostgreSQL database'

A .dev-services.yml.example file with common service configurations is created when you run the React on Rails generator.

Configuration Fields

  • check_command (required): Shell command to check if the service is running
  • expected_output (optional): String that must appear in the command output
  • start_command (optional): Command to start the service (shown in error messages)
  • install_hint (optional): How to install the service if not found
  • description (optional): Human-readable description of the service

Behavior

If .dev-services.yml exists, bin/dev will:

  1. Check each configured service before starting
  2. Show a success message if all services are running
  3. Show helpful error messages with start commands if any service is missing
  4. Exit before starting the Procfile if services are unavailable

If .dev-services.yml doesn't exist, bin/dev works exactly as before (zero impact on existing installations).

Example Output

When services are running:

🔍 Checking required services (.dev-services.yml)...

✓ redis - Redis (for caching and background jobs)
✓ postgresql - PostgreSQL database

✅ All services are running

When services are missing:

🔍 Checking required services (.dev-services.yml)...

✗ redis - Redis (for caching and background jobs)

❌ Some services are not running

Please start these services before running bin/dev:

redis
Redis (for caching and background jobs)

To start:
redis-server

Not installed? brew install redis (macOS) or apt-get install redis-server (Linux)

💡 Tips:
• Start services manually, then run bin/dev again
• Or remove service from .dev-services.yml if not needed
• Or add service to Procfile.dev to start automatically

Database Connectivity Check

bin/dev automatically checks that your Rails database is accessible before starting the development server. This catches common issues like a missing database or a stopped database server, and provides clear error messages with specific commands to fix the problem.

Behavior

When bin/dev starts, it runs a quick Rails runner process to verify:

  1. The database exists and accepts connections
  2. Migrations are up to date (warns but does not block if pending)

If the database is not accessible, bin/dev prints a clear error message and exits before starting any processes.

Note: This check adds ~1-2 seconds to startup time as it spawns a Rails runner process.

Disabling the Check

There are three ways to disable the database check, listed by priority:

  1. CLI flag (highest priority):

    bin/dev --skip-database-check
  2. Environment variable:

    SKIP_DATABASE_CHECK=true bin/dev
  3. Configuration in config/initializers/react_on_rails.rb:

    ReactOnRails.configure do |config|
    config.check_database_on_dev_start = false
    end

When to disable:

  • Apps that don't use a database (API-only backends with external data stores)
  • Rapid restart workflows where the 1-2 second overhead matters (e.g., TDD with guard/watchman)
  • Projects where ActiveRecord is not loaded

Security Note

⚠️ IMPORTANT: Commands in .dev-services.yml are executed during bin/dev startup without shell expansion for safety. However, you should still:

  • Only add commands from trusted sources
  • Avoid shell metacharacters (&&, ||, ;, |, $, etc.) - they won't work and indicate an anti-pattern
  • Review changes carefully if .dev-services.yml is committed to version control
  • Consider adding to .gitignore if it contains machine-specific paths or sensitive information

Recommended approach:

  • Commit .dev-services.yml.example to version control (safe, documentation)
  • Add .dev-services.yml to .gitignore (developers copy from example)
  • This prevents accidental execution of untrusted commands from compromised dependencies

Execution order:

  1. Database connectivity check (unless disabled)
  2. Service dependency checks (.dev-services.yml)
  3. Precompile hook (if configured in config/shakapacker.yml)
  4. Process manager starts processes from Procfile

Installing a Process Manager

Overmind provides easier debugging and better signal handling:

# macOS
brew install overmind

# Linux
# See: https://github.com/DarthSim/overmind#installation

Foreman (Alternative)

Foreman is a widely-used Ruby-based process manager:

# Install globally (NOT in Gemfile)
gem install foreman

Important: Do NOT add Foreman to your Gemfile. Install it globally on your system.

Why not in Gemfile?

From Foreman's documentation:

Foreman is not a library, and should not affect the dependency tree of your application.

Key reasons:

  • Dependency conflicts: Including Foreman in your Gemfile can create dependency conflicts that break other projects
  • Security risk: Loading Foreman as an application dependency creates an unnecessary security vulnerability vector
  • Stability: Foreman is mature and stable; bundling it could introduce bugs from unnecessary dependency updates
  • Wrong abstraction: Foreman is a system tool, not an application dependency

Install Foreman globally: gem install foreman

Alternative: Run Process Managers Directly

You can also run process managers directly instead of using bin/dev:

# With Overmind
overmind start -f Procfile.dev

# With Foreman
foreman start -f Procfile.dev
warning

When running Foreman directly (not via bin/dev), Foreman injects its own PORT environment variable (starting at 5000) into every subprocess. This causes ${PORT:-3000} in Procfile.dev to evaluate to Foreman's injected value rather than the fallback 3000.

To avoid this, set PORT explicitly in your shell or .env file before running Foreman:

PORT=3000 foreman start -f Procfile.dev
# or add PORT=3000 to your .env file

bin/dev handles this automatically — port detection runs before Foreman starts, so PORT is always set correctly when Foreman launches.

Customizing Your Setup

Edit Procfile.dev in your project root to customize which processes run and their configuration.

The default Procfile.dev includes:

rails: bundle exec rails s -p ${PORT:-3000}
dev-server: bin/shakapacker-dev-server
server-bundle: SERVER_BUNDLE_ONLY=true bin/shakapacker --watch

Running Multiple Worktrees Simultaneously

If you use git worktrees to work on multiple branches in parallel, bin/dev automatically detects and avoids port conflicts — no configuration needed.

When the default ports (3000 for Rails, 3035 for webpack-dev-server) are occupied, bin/dev scans for the next free pair and prints:

Default ports in use. Using Rails :3001, webpack :3036

To override ports manually, create a .env file in the worktree (gitignored by default). A .env.example is generated by rails g react_on_rails:install as a reference:

PORT=3001
SHAKAPACKER_DEV_SERVER_PORT=3036

When PORT or SHAKAPACKER_DEV_SERVER_PORT are set, auto-detection is skipped entirely.

Coding Agent / CI Integration

When using coding agent tools that run multiple workspaces concurrently (Conductor.build, OpenAI Codex, Quad Code, etc.), set REACT_ON_RAILS_BASE_PORT to derive all service ports from a single value. This eliminates the need for per-worktree .env files.

Important: Run each live bin/dev stack from a separate checkout, worktree, or copied app directory. Starting two stacks from the exact same app path is not supported because build tools such as ReScript and webpack watchers share lock files and runtime artifacts within that directory.

bin/dev assigns ports using fixed offsets from the base:

ServiceOffsetExample (base=4000)
Rails server+04000
Webpack dev server+14001
Node renderer (Pro)+24002
(reserved)+3–+94003–4009

When a base port is detected, bin/dev also sets RENDERER_PORT and REACT_RENDERER_URL automatically so the Pro Node Renderer and Rails initializer agree on the port without any additional configuration.

Heads up: setting RENDERER_PORT, RENDERER_URL, or REACT_RENDERER_URL in your environment activates the Pro renderer code path even in OSS apps — in base-port mode this means RENDERER_PORT and REACT_RENDERER_URL will be derived from the base and propagated to spawned processes. If you use RENDERER_PORT in your environment for an unrelated purpose, rename your variable to avoid the side effect.

Note: Base-port mode derives the node renderer URL as http://localhost:<port>. If you run the renderer in a Docker container or on a remote host (e.g. REACT_RENDERER_URL=http://renderer:3800), do not use base-port mode — set REACT_RENDERER_URL explicitly and use the manual worktree setup instead. bin/dev will warn at runtime if a pre-set REACT_RENDERER_URL is overridden.

Recognized environment variables (checked in order):

  1. REACT_ON_RAILS_BASE_PORT — the canonical base port variable; any tool can set this.
  2. CONDUCTOR_PORT — set automatically by Conductor.build.

Note on CONDUCTOR_PORT: React on Rails treats CONDUCTOR_PORT as the base of a consecutive port block (Rails = base + 0, webpack = base + 1, renderer = base + 2). This interpretation is not part of a public Conductor API; treat CONDUCTOR_PORT support as best-effort until Conductor documents the contract. If a future Conductor release redefines CONDUCTOR_PORT (for example, to mean the Rails port itself), override by setting REACT_ON_RAILS_BASE_PORT explicitly — it takes precedence and uses the same derivation rules.

Priority chain: base port > explicit per-service env vars (PORT, etc.) > auto-detect free ports.

Example: setting the base port in a tool's configuration:

# In your agent tool's workspace setup or .env
REACT_ON_RAILS_BASE_PORT=4000

Tool-specific setup

Conductor.build sets CONDUCTOR_PORT for you — no configuration needed. Other tools (Claude Code CLI, OpenAI Codex CLI or app, plain git worktree, etc.) don't inject a port variable, so pick a different base per checkout using one of the options below.

  • Per-worktree .env file (tool-agnostic; gitignored by default):

    # .env at the root of each worktree
    REACT_ON_RAILS_BASE_PORT=4000
  • Claude Code .claude/settings.json (per-project, checked in or local):

    {
    "env": {
    "REACT_ON_RAILS_BASE_PORT": "4000"
    }
    }
  • Shell export (ad hoc, one session):

    REACT_ON_RAILS_BASE_PORT=4000 bin/dev

bin/dev reads the variable from the process environment regardless of how it was set, so mix and match whichever is most convenient for each tool.

If you create a new checkout by copying an existing app directory while another bin/dev is running, the copy can inherit tmp/pids/server.pid or Overmind socket files from the original app. bin/dev now cleans those copied stale runtime files automatically on startup. If you launch processes outside bin/dev, clear those files yourself before booting the copied app.

Manual Worktree Port Setup (Pro)

If you use the Node Renderer (React on Rails Pro) with manual worktrees (no base port), you need to configure the renderer port in addition to the standard Rails and webpack ports:

# .env in each worktree
PORT=3001
SHAKAPACKER_DEV_SERVER_PORT=3036
RENDERER_PORT=3801
REACT_RENDERER_URL=http://localhost:3801

The renderer port must match on both sides: RENDERER_PORT is read by the Node process and REACT_RENDERER_URL is read by the Rails-side Pro initializer.

Note: bin/dev kill only stops the renderer when RENDERER_PORT (or REACT_RENDERER_URL) is exported in the current shell. From a fresh terminal that hasn't sourced your worktree's .env, run e.g. RENDERER_PORT=3801 bin/dev kill, or source .env first. Otherwise the renderer process will silently keep running.

See Also