Setting Up a Cloudfront CDN for Rails

Update: I’ve since added a post on serving fonts for Firefox with Fastly.

Implementing a CDN to serve compiled assets in Rails is one of the most significant performance enhancements I’ve seen. Having done so for 2 fairly large applications, it’s hard for me to imagine serving assets locally anymore.

The Asset Pipeline

The addition of the Rails asset pipeline has reduced both the number of assets served and the size of these files. Users on slow connections have been the biggest beneficiary of these changes.

The Problem

Even with the asset pipeline, content is transmitted to the end user over their, sometimes slow, connection. Distance plays a role in the speed at which data can be delivered. Because of this, users further away from your servers get a sluggish experience. This decreases both the users’ patience and your ability to effectively engage them.

The Solution

CDNs are networks of servers that host your content so that when you make a request, the request is served from a server closest to you. This can often reduce transmission time by several seconds (…think users in Asia requesting content that’s served from Virginia - this is the case if you deploy to Heroku).

The use of a CDN also reduces the number of requests to your application servers. Most Ruby applications use Nginx or Apache in front of the Ruby processes. These HTTP servers are really good at serving static content, but no one will deny - the fewer requests, the better.

Option 1: Push your assets to the CDN during deployment

Depending on your method of deployment, pushing assets to a CDN isn’t always trivial. The asset_sync gem has made this relatively straight forward if you choose to host your assets from Amazon S3. Out of the 2 options, this is the most efficient because all requests for assets will be diverted away from your application, leaving its precious processing power to serve more important application requests.

Option 2: Assets are pulled to the CDN on first request

This option won’t change deployment and is simple to setup. The only downside is that upon first request to an asset, the CDN will pull it from your web server and cache it (it’s hardly a downside if you’re currently serving all your assets from your web server). All subsequent requests to that asset will be served straight from the CDN. The simplicity of this option generally makes it my preferred option.

So let’s get to it…

Amazon Cloudfront

Log in to your Amazon EC2 account and click “Cloudfront”:

"Click Cloudfront in the AWS web console"

Click “Create Distribution”:

"Create a Cloudfront distribution endpoint"

Enter the domain where your assets currently live (ignore Origin ID - it’ll be filled in for you):

"Settings for a typical CDN"

Make note of the Cloudfront distribution URL

"Cloudfront distribution URL"

Rails

Rails provides and easy way to change the host URL of the assets (images, stylesheets, javascripts, fonts…). Enter the Cloudfront distribution URL from above as the Rails asset_host.

# config/environments/production.rb
config.action_controller.asset_host = "d24xjtg100euk4.cloudfront.net"

At this point, the domain of all Rails asset helpers image_tag, stylesheet_link_tag, and javascript_include_tag will be prefaced with the asset host URL that you configured above.

For example:

image_tag("shark_teeth.png")
# http://d24xjtg100euk4.cloudfront.net/assets/images/shark_teeth.png

Note: if you only change config/environments/production.rb, you won’t see any changes in your development environment.

And that’s it!

In Summary

This is the ultimate low-hanging fruit optimization. If you haven’t served your assets from a CDN before, I’d suggest giving it a try. The cost of Cloudfront is minimal, and in my mind, worth 10x that.

I’ve recently been trying out a service called Fastly, which is an alternative to Cloudfront. It’s slightly more expensive, but seems to have better and more consistent performance.