Building Real-time Apps with Rails 8, Hotwire & ActionCable in Production

Chandra Shettigar - Feb 25 - - Dev Community

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.)
Enter fullscreen mode Exit fullscreen mode

How it Works in Local:

  1. The browser opens a WebSocket connection to /cable.
  2. Turbo Streams subscribe to updates using turbo_stream_from.
  3. 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.

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)

Enter fullscreen mode Exit fullscreen mode

How it Works in Production:

  1. The browser opens a WebSocket connection via the load balancer and ingress controller.
  2. ActionCable listens for changes in either Redis or the database (Rails 8 default).
  3. 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).
  4. 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
Enter fullscreen mode Exit fullscreen mode

Or Mysql

production:
  adapter: mysql2
  url: mysql2://prod-db-host/myapp_prod
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Attach It to the Messages Box

<div id="messages" data-controller="chat">
  <%= turbo_stream_from "chat_room_#{chat_room.id}" %>
  <%= render @messages %>
</div>
Enter fullscreen mode Exit fullscreen mode

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.
. . . . . . . . . . . . . . . . .