Skip to main content

Command Palette

Search for a command to run...

Why rails s Ignores Your Puma Binds (The Hidden PORT Trap)

Updated
2 min read

TL;DR

- ENV["PORT"] → treated as user-supplied by Rails
- Puma prioritizes user config → calls clear_binds!
- All binds in config/puma.rb are ignored
- Result: single bind only
- Fix: use MY_APP_PORT instead

Problem

config/puma.rb defines multiple binds, but rails s results in a single bind.

Expected

bind "tcp://127.0.0.1:3000"
bind "tcp://172.17.0.1:3000"
bundle exec puma

→ both binds active

Actual

rails s

→ single bind (localhost:PORT)

Execution Path

rails s → Rails::Server → Rack → Puma
# https://github.com/rails/rails/blob/v8.0.4/railties/lib/rails/commands/server/server_command.rb
user_supplied_options << :Port if ENV["PORT"]

Root Cause

Tier Source
user_config env / CLI
file_config puma.rb
default_config internal

First tier defining :binds wins (no merge)

# https://github.com/puma/puma/blob/v7.2.0/lib/rack/handler/puma.rb#L96-L116
config.clear_binds! if host || port
# https://github.com/puma/puma/blob/v7.2.0/lib/puma/dsl.rb#L294-L296
def clear_binds!; @options[:binds]=[]; end

Flow: PORT → user-supplied → userconfig → clearbinds! → file binds ignored

Observed

Command PORT Result
rails s no all binds
rails s yes single bind
puma any all binds

Fix

app_port = ENV.fetch("MY_APP_PORT",3000)
bind "tcp://127.0.0.1:#{app_port}"
bind "tcp://172.17.0.1:#{app_port}"
MY_APP_PORT=3000 rails s

Summary

  • Rails promotes PORT → user config

  • Puma replaces binds (no merge)

  • Multi-bind requires PORT unset