Deploying ActionCable with a Single Server

In a series of posts from a few months back (ActionCable Part I, Action Cable Part II), I explored Rails' ActionCable and built out an app in which users can post code snippets in chat room-like forums in real-time.

In those posts, we saw ActionCable running on a separate, standalone server. As much fun as it was to deploy that app (read: not fun), ActionCable has since been merged into Rails and can now be run alongside your main Rails app, on a single server.

Given this development (and with the encouragement of a few folks out there on the internet), I decided to re-work and re-deploy my earlier application using a single server. I found it to be a pretty seamless transition and the deployment was roughly 100% less painful than previously.

The App

What this app actually does doesn't matter! If you want to learn more about how to implement ActionCable to build out real-time updates, like messaging between users, check out this earlier post. This post is about configuring and deploying ActionCable in Rails on a single server. So, we're assuming we're working with an app that already has ActionCable built out on the client side and on the server side. Here, we'll configure our server to run the cable server in conjunction with our main app, and we'll configure Redis.

Let's get started.


Configuring Action Cable in Development


Configure Puma

ActionCable can be run together with your Rails app, on a single server, with any threaded server. For this app, I chose Puma, although ActionCable can be implemented just as well with Unicorn or Passenger. Check out this article from Go Rails to see ActionCable used with Passenger.

Add gem 'puma' to your Gemfile and bundle install. Then, set up Puma in config/puma.rb:

workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['MAX_THREADS'] || 5)
threads threads_count, threads_count

preload_app!

rackup      DefaultRackup
port        ENV['PORT']     || 3000
environment ENV['RACK_ENV'] || 'development'

on_worker_boot do
  # Worker specific setup for Rails 4.1+
  # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
  ActiveRecord::Base.establish_connection
end

Configure Redis

Create a file, config/redis/cable.yml, in which to specify the Redis URL for each environment:

local: &local  
  :url: redis://localhost:6379
  :host: localhost
  :port: 6379
  :timeout: 1
  :inline: true
development: *local  
test: *local 

Create a file, config/initializers/redis.rb:

uri = URI.parse(ENV["REDISTOGO"] || "redis://localhost:6379/" )
REDIS = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)

Configure the Cable Server

In your routes file, add the following:

mount ActionCable.server => '/cable'

This will allow you to establish a Web Socket connection, listening for requests on /cable.

Next, we set the server URL in config/development.rb:

config.action_cable.url = "ws://localhost:3000/cable"

Lastly, configure the client. In this case of this particular app, the Lab model acts as the conversation or chat room. So, our consumer code belongs in app/assets/javascripts/channels/labs.js.erb:

//= require cable
//= require_self
//= require_tree .

this.App = {};

App.cable = Cable.createConsumer();

Lastly, add the following to your app/views/layouts/application.html.erb in the head:

<%= action_cable_meta_tag %>

By using this tag, we can create our consumer without specifying the server URL as the argument to the createConsumer function. Instead, we specify the URL in the development and production environment files. This allows us to easily manage our cable connection in both development and production without having to constantly switch back and force between different server URL specifications in the createConsumer function.

Now, a new instance of ActionCable will be created every time a new instance of our server is created. If you connect to your server with $ rails s Puma, you should see your ActionCable feature working.


Depolying ActionCable to Heroku


First, run heroku create in the directory of your app.

Configure Puma for Production

We need to set Puma as the server for our web processes. In the Procfile:

web: bundle exec puma -p $PORT

Configure Redis for Production

Install the Redis To Go add-on:

$ heroku addons:create redistogo

Confirm your Redis To Go URL with:

$ heroku config --app <your fancy app name> | grep REDISTOGO_URL

You should see your URL returned below. It will look something like this:

REDISTOGO_URL              => redis://redistogo:44ec00004dd4a5afe77a649acee7a8f3@thing.redistogo.com:9093/

Next, open up config/redis/cable.yml and configure Redis for the production environment:

development: &development
  :url: redis://localhost:6379
  :host: localhost
  :port: 6379
  :timeout: 1
  :inline: true
test: *development
production: &production
  :url: redis://redistogo:44ec00004dd4a5afe77a649acee7a8f3@thing.redistogo.com:9093/
  :host: thing.redistogo.com
  :port: 11662
  :password: 44ec00004dd4a5afe77a649acee7a8f3
  :inline: true
  :timeout: 1

Configure the Cable Server

We need to set the cable URL for our production environment:

# config/environments/production.rb

config.web_socket_server_url = "wss://your-amazing-app.herokuapp.com/cable"

Allow Request Origins

The Web Socket connection can only accept requests from allowed hosts. In development, this defaults to localhost:3000. In production, we need to specify the URL of our app, i.e. the URL from which requests to the socket connection will be sent.

In app/config/environments.production.rb

 Rails.application.config.action_cable.allowed_request_origins = ['<your-amazing-app>.herokuapp.com']

And that's it! We should be able to successfully git push heroku master and have our ActionCable feature running in production. A few things before you go...

Gotchas

A few of the pain points I ran into:

  • ActionCable and Spring do not play nice. If you're building your app from scratch, run rails new --skip-spring. Otherwise, comment out or remove Spring from your Gemfile.
  • If you want to use the Rails 5 pre-release, run gem install rails --pre before generating your new Rails app. If you are sticking with Rails 4, remember to include the ActionCable gem. In your Gemfile:
gem 'actioncable', github: 'rails/actioncable', branch: 'archive'
gem 'celluloid', '~> 0.16.0'

subscribe and never miss a post!

Blog Logo

Sophie DeBenedetto

comments powered by Disqus
comments powered by Disqus