Serve Precompiled Rails Assets from a Static Domain

Precompile and deliver your Rails assets from a different domain for static content

With the release of the Rails 3.1 Asset Pipeline, which uses Sprockets to deliver Sass and Coffeescript rendered assets, the development of properly organized assets in Rails just got much easier. Now there exist many tutorials out on the web that go into detail about how to play around with this new feature, so I’m not going to go into detail about how todo that. What I will cover is how to setup a static domain, that does not interfere with your Rails daemon, that serves these assets quickly and smoothly.

Last Updated

This blog entry was last updated on September 3rd 2011 and was first published on September 3rd 2011

Why should this be considered

The major reason why this should be considered is because Rails itself should not have to handle serving asset files regardless if they’re precompiled or not. Back with Rails 3.1 RC4 and RC5 I noticed a huge performance lag with my stylesheet and javascript files whenever they were being served in production mode. Even when they were precompiled, Rails was serving the assets for me and after trying to disable the serve static assets boolean in my production file, this was still slowing down my server. By offering a different domain (or subdomain) to serve the assets, all of the assets themselves can be managed elsewhere. Note there are also various other benefits to serving asset files from a different domain entirely (this is explained in the next section).

Domains, Subdomains or just a different port?

Its better to serve assets from a different domain from your www domain. I don’t mean a subdomain, but an entirely different domain. This is because when an asset is fetched from the same domain as your website, the user agent that fetches that asset will upload any cookie data that is stored for that domain and that matches that path. Since Rails stores session data in cookies by default, you can imagine how much unnecessary data is being transfered back and forth just to fetch some asset files. With subdomains, this issue is almost gone, but there could be some cookies in your website that may exist for all domains (www, subdomains and no subdomains). If you use a different port to serve assets then this issue is no different from serving it from the same port as the Rails app.

Modern browsers will download only a certain amount of assets in parallel from a single host. This means that if use another domain with various other subdomains connected to that domain then you can download almost all your assets in parallel once the page is loaded.

How do we set this up?

Easy, just configure your NGINX or Apache server to serve assets from a specified directory which never changes and then configure the headers for caching and expiry to their max. Then setup capistrano to rotate the assets each time a deployment is issued.

Step 1 – Get the domain(s)

Whether or not you already have a static domain purchased yet, make sure that you either have a new domain or a subdomain of your primary domain ready so that both domains resolve to the same ip address.

Step 2 – Configure your HTTP Daemon

In this article I will only cover how to setup your NGINX + Passenger HTTP daemon:

NGINX

If you installed NGINX via passenger for Rails (which I hope you did) then just hop into your nginx.conf file and add the following:

# Static Server
server {
    listen 80;
    server_name staticdomain.com;
    root /var/www/staticdomain.com;
    location ~* \.(ico|css|js|gif|jpe?g|png)(\?[0-9]+)?$ {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
        break;
    }
}

# Rails Server
server {
    listen 80;
    server_name dynamicdomain.com;
    root /var/www/dynamicdomain.com/current/public;
    passenger_enabled on;
}

This way, any static files will be cached properly and no cookie data will be sent to the static files each time an asset is requested.

Step 3 – Configure Rails

Be sure to set the asset_host in Rails to that of the domain that you will be using in production mode. Be sure to set the following configurations in your production.rb file:

#set the asset host
config.action_controller.asset_host = "http://staticdomain.com"

Step 4 – Configure Capistrano

Lets now configure capistrano to precompile your assets just after it has updated and symlinked the code. Once the assets are precompiled, then replace the existing assets with your newly updated assets.

task :start, :roles => :app do
    asset_path = "/var/www/staticdomain.com/" #this needs to point to where your assets are saved
    run "cd #{current_release}; rake assets:precompile" #compile the assets
    run "rm -fr #{asset_path}/*" #move the asset files to the asset_path directory
    run "mv #{current_release}/public/assets/* #{assets_link_path}" #move the asset files to the asset_path directory
    run "cd #{current_release}; rake assets:clean" #remove the existing assets
end

Now whenever a cap deploy command is issued, the assets are uploaded to the asset server and updated.

Room for improvement…

If anyone here knows of a better way to hotswap the newly uploaded asset fiels with the existing asset files without having to delete the existing asset files then that would help out a lot. The reason why I use rm -fr + mv is because the asset files within any subdirectories can only be copied over with a move operation (a copy operation will overwrite the entire directory).

I attempted to solve this by using a symlink operation where the files are linked over to the new assets directory whenever its uploaded. This does not work because NGINX will register the direct path of a symlink whenever it is restarted (this is how passenger works by restarting the mongrel or webrick instance when a capistrano restart call is issued). If anyone knows how to get around this then please let me know in the comments below.

One Response to “Serve Precompiled Rails Assets from a Static Domain”

  1. Nice article, and site for that matter. With config examples and everything.

    About the room for improvement: I would have thought rsync is the tool for the job. I just learned that rsync does not have to be r as in remote, but will handle local – local updates just as well.

    Alas, I am no expert on the subject by any means, but I figure that since ccc uses it for local backup, you should be able to get it to do what you want there.

Add Your Comment