Rails 8 brings big improvements to real-time updates with Hotwire and ActionCable. Locally, everything works smoothly—your browser talks directly to the Rails app, and WebSockets handle real-time updates instantly. But in production, things are different:
- Web traffic passes through load balancers and Kubernetes (or other orchestration tools).
- Jobs run in separate containers, not inside your Rails app.
- Redis is no longer required for ActionCable — it can now use your database (MySQL/PostgreSQL).
Let's compare Rails 8 real-time setup in local vs. production and walk through the key changes you need to make.
1. How Rails 8 Handles Real-time Updates Locally
In local development, everything is simple:
- Your browser connects directly to Rails (Puma server).
- WebSockets are handled inside Puma (
/cable
endpoint). - Jobs run inside Puma (if using Async adapter).
Flow in Local Setup:
Browser → Puma (Rails) → WebSockets (ActionCable)
↑ ↓
Jobs (ActiveJob, Sidekiq, etc.)
How it Works in Local:
- The browser opens a WebSocket connection to
/cable
. - Turbo Streams subscribe to updates using
turbo_stream_from
. - When a new message is created in Rails, it:
- Calls
broadcast_append_to
, which sends the update over WebSockets. - The UI updates instantly, no page reload needed.
- Calls
Easy setup—no external dependencies like Redis or a separate database for WebSockets.
2. How Rails 8 Handles Real-time Updates in Production
Once you deploy to AWS + Kubernetes, things change:
- Browsers don’t directly talk to Puma. Instead, traffic goes through a load balancer (AWS ALB, NGINX Ingress, etc.).
- Jobs run in separate pods (like Sidekiq workers).
- WebSockets need to work across multiple app instances.
- Redis is no longer required — Rails 8 supports database-backed pub/sub for ActionCable.
Flow in Production Setup:
Browser → AWS Load Balancer → Kubernetes Ingress → Rails Pod (Puma)
|
├→ Sidekiq Pod (Runs Jobs)
├→ Database (Stores ActionCable Messages)
How it Works in Production:
- The browser opens a WebSocket connection via the load balancer and ingress controller.
- ActionCable listens for changes in either Redis or the database (Rails 8 default).
- When a job (e.g., a new chat message) runs in a separate container, it:
- Inserts a message into the database (if using DB pub/sub).
- OR Publishes to Redis (if using Redis pub/sub).
- ActionCable picks up the new message and sends it to all subscribed clients via WebSockets.
- Database-backed ActionCable (Rails 8) reduces dependency on Redis.
- Jobs can now update WebSockets even if they run in separate pods.
3. Setting Up WebSockets in Production
Step 1: Configure ActionCable to Use the Database
In Rails 8, you can now store WebSocket updates in MySQL or PostgreSQL instead of Redis.
Edit config/cable.yml
:
production:
adapter: postgresql
url: postgresql://prod-db-host/myapp_prod
Or Mysql
production:
adapter: mysql2
url: mysql2://prod-db-host/myapp_prod
Now, ActionCable will store real-time updates in the database instead of Redis.
Step 2: Create the ActionCable Database Table
Run the migration:
bin/rails action_cable:install
bin/rails db:migrate
This creates a pub/sub table for WebSocket messages.
4. Handling WebSockets with Load Balancers & Kubernetes
AWS Load Balancer: Enable WebSockets
If you're using AWS ALB, make sure WebSockets are enabled:
aws elbv2 modify-listener --load-balancer-arn YOUR_LB_ARN --protocol HTTP2
Kubernetes Ingress: Allow WebSocket Connections
If using NGINX Ingress, add WebSocket support in ingress.yaml
:
nginx.ingress.kubernetes.io/websocket-services: "rails-app"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
Now, WebSocket connections won’t be blocked by the load balancer.
5. Updating Real-time Messages in Production
Using Turbo Streams in Rails 8
In your Message model, automatically broadcast updates:
class Message < ApplicationRecord
after_create_commit do
broadcast_append_to "chat_room_#{chat_room_id}", target: "messages"
end
end
This will work locally and in production, regardless of whether you use Redis or the database.
6. Stimulus for Real-time UI Updates
We can use Stimulus to auto-scroll chat messages when new ones arrive.
Create chat_controller.js
:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
connect() {
this.element.addEventListener("turbo:before-stream-render", this.newMessage.bind(this));
}
newMessage() {
this.element.scrollTop = this.element.scrollHeight;
}
}
Attach It to the Messages Box
<div id="messages" data-controller="chat">
<%= turbo_stream_from "chat_room_#{chat_room.id}" %>
<%= render @messages %>
</div>
Now, new messages trigger turbo:before-stream-render
, scrolling the chat automatically.
7. Summary: Rails 8 Local vs. Production
Feature | Local (Puma Only) | Production (AWS + K8s) |
---|---|---|
WebSockets | Handled by Puma | Needs ALB + Ingress Config |
Job Execution | Inside Puma | Runs in Sidekiq Pod |
Broadcast Mechanism | Direct Turbo Stream | DB Pub/Sub OR Redis |
Scaling | Works out of the box | Requires WebSocket Load Balancing |
8. Final Thoughts
With Rails 8, real-time updates are easier than ever: No more Redis dependency—ActionCable can now use MySQL/PostgreSQL.
- Works with Kubernetes and AWS Load Balancers.
- Turbo Streams + Stimulus keep things simple.
Top comments (0)