Deployment Guide

peakpg edited this page Dec 18, 2013 · 10 revisions
Clone this wiki locally

This guide covers how to setup the production environment for your BrowserCMS application. This setup should work for both:

  • Apache (mod_rails) with Passenger
  • Nginx with Passenger (though it's less well tested than Apache)

This guide is valid as of BrowserCMS 3.5.0.rc1.

Configure the Production Environment

In order for the CMS to send emails, you need to edit the following lines on production.rb:

# This should match the URL for your public CMS site.
config.cms.site_domain = "www.example.com"

# Configure your mail server host name
config.action_mailer.smtp_settings = {:address => 'mailserver.example.com', :domain => config.cms.site_domain}

Setting Up Virtual Hosts

In order to make page caching work, you need to create two virtual hosts for your site. The desired endstate is to end up with two top level domains. For example, assuming our domain is example.com, we want to have the following domains when we are done:

  • www.example.com - The 'public' access to the site, which is uses page caching for speed.
  • cms.example.com - The 'admin' access to the site, which allow editors to make changes.

Why not drop the www?

Its a good idea to set up a redirect from example.com to www.example.com for ease of access and SEO purposes. The main reason to use www.example.com as the 'canonical' domain rather then dropping the www is due to some browsers (IE) having issues with cookies on example.com being visible to cms.example.com. Admin users will have hard to diagnose problems when they are logged in.

What if I want a single domain?

Some hosting setups may not like subdomains (or are harder to setup). You can run the CMS off a single domain by setting the following:

# In production.rb
config.cms.use_single_domain = true

This will disable all page caching though, which has performance implications.

Apache Configuration

Here is the sample configuration for Apache plus Passenger.

<VirtualHost *:80>
  ServerName cms.mysite.com
  DocumentRoot "/var/sites/mysite/public"
  RailsEnv production
  <directory "/var/sites/mysite/public">
    Order allow,deny
    Allow from all
  </directory>
</VirtualHost>

<VirtualHost *:80>
  ServerName mysite.com
  DocumentRoot "/var/sites/mysite/public"
  RailsEnv production
  RewriteEngine On

  # Uncomment for rewrite debugging
  #RewriteLog /var/sites/mysite/log/rewrite.log
  #RewriteLogLevel 9

  # Ensure we are not serving pages with trailing slashes
  RewriteRule ^(.+)/$  $1 [R=301,L]

  # Apache should serve cached pages
  RewriteRule ^/$ /cache/index.html [QSA]
  RewriteRule ^([^.]+)$ /cache/$1.html [QSA]

  <directory "/var/sites/mysite/public">
    Order allow,deny
    Allow from all
  </directory>
</VirtualHost>

Nginx

Here is a sample configuration for Nginx.

server {
        listen 80;
        server_name cms.example.com
                    www.cms.example.com;

        # remove 'www'
        if ($host ~* ^www\.(.*)) {
         set $remove_www $1;
         rewrite ^(.*)$ http://$remove_www$1 permanent;
        }

        # point to public
        root /home/example.com/www/public;

        # if you use reverse proxy (in case of multiple rubies)
        # point to your ip/port in location
        # if you use only one ruby with passenger (for example REE) skip this location block 
        location / {
          proxy_pass http://127.0.0.1:3001;
          proxy_set_header Host $host;
        }

        # passenger related stuff
        passenger_enabled on;

     }

     server {
       listen 80;
       server_name example.com
                   www.example.com;

       # remove 'www'
        if ($host ~* ^www\.(.*)) {
         set $remove_www $1;
         rewrite ^(.*)$ http://$remove_www$1 permanent;
        }

        # Cache Index HTML Files
        if (-f $document_root/cache/$uri/index.html) {
                rewrite (.*) /cache/$1/index.html break;
        }

        # Cache HTML Files
        if (-f $document_root/cache/$uri.html) {
                rewrite (.*) /cache/$1.html break;
        }

        # Cache Catch all
        if (-f $document_root/cache/$uri) {
                rewrite (.*) /cache/$1 break;
        }

        # point to public
        root /home/example.com/www/public;

        # if you use reverse proxy (in case of multiple rubies)
        # point to your ip/port in location
        # if you use only one ruby with passenger (for example REE) skip this location block 
        location / {
          proxy_pass http://127.0.0.1:3001;
          proxy_set_header Host $host;
        }

        # passenger related stuff
        passenger_enabled on;
}

I believe there's a better way to do it. Main idea is to listen to domains and sub domains in one 'server' block, but if user comes from sub domain we should skip caching statements. Unfortunately nginx doesn't support multiple conditions neither nested "if's" to check whether request came from sub domain or not.

Setting up production database

When you deploy your project to a remote server for the first time, you need to do migrate/populate the database on your production server. BrowserCMS provides a nice shortcut to setup your database in one go. After pushing your code's project, run:

rake db:install

Calling this task is essentially the same as calling rake db:create db:migrate db:seed. Take note of the generated password for the cmsadmin user.

Warning: Copying a local database

If you choose to copy a local database to your production server, make sure you change the default cmsadmin password to something non-guessable for security purposes.

Using Capistrano to Deploy

If you plan to use Capistrano to deploy your sites, you will likely need to make some additional configuration changes.

File Upload location

The default location where files are stored is tmp/uploads, which means capistrano deployments will cause broken links after deploying. To fix this, you can reconfigure where files are stored to use a shared directory.

# In config/environments/production.rb
config.cms.attachments.storage_directory = File.join(Rails.root, 'uploads') 

Then update config/deploy.rb (which Capistrano should generate) so that it creates an shared/uploads directory, and a symlink to it from within your project.

# In config/deploy.rb
task :link_shared_directories do
  run "ln -s #{shared_path}/uploads #{release_path}/uploads"
end

after "deploy:update_code", :link_shared_directories

The binary data for uploaded files will be placed in shared/uploads and the CMS will serve the data from there.

Better File performance

One way to improve the performance of BrowserCMS is to enable X-Sendfile. Used in conjunction with Web servers like Apache and Nginx, X-Sendfile will allow web servers to handle serving files that have been uploaded into the CMS. Web servers are very well optimized for sending static files, and doing so takes load off the Ruby processes reducing bottlenecks.

To enable X-Sendfile in your application, uncomment one of the following two lines depending on which web server you are using.

# In config/environments/production.rb
config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx

You will then need to configure your web server to handle X-Sendfile requests. See documentation for Apache and Nginx for details.

Configuring the Asset Pipeline

The Rails asset pipeline will need to be configured correctly so that assets can be compiled in production. There are two ways to handle assets:

  1. On the fly asset compilation
  2. Precompile assets

See http://guides.rubyonrails.org/asset_pipeline.html#in-production for some more details.

Option 1: On the fly

The easiest way to handle this (especially for testing) is to let assets be compiled on the server. Edit the following files:

# In config/environments/production.rb
# Setting this to true allows assets to be compiled as requested, rather than precompiled.
config.assets.compile = true

Next update the Gemfile to allow for asset compilation on the server. therubyracer is a JS runtime that may be needed to on some server environments which don't come with one preinstalled.

# In Gemfile
group :production do
  gem 'uglifier' 
  gem 'therubyracer'
end

Option B: Precompiling

This option assumes you are using capistrano for deployment. Add the following to your config/deploy.rb:

set :rake, '/absolute/path/to/rake/on/server' # Figure this out by doing `which rake` on server
load 'deploy/assets'

Modify config/environments/production.rb to reflect the following (specifically these notes are for 3.5.x):

config.assets.precompile += %w( cms/core_library.js cms/taglist.css cms/date_picker.css cms/sitemap.css cms/page_toolbar.css cms/selectbox.css cms/content_library.js cms/dashboard.css cms/toolbar.js bcms/ckeditor_load.js cms/administration.css cms/login.css cms/form_layout.css cms/content_library.css ckeditor-jquery.js ckeditor_standard_config.js cms/block.css)
config.assets.initialize_on_precompile = false
config.assets.compile = false

The list of files to be precompiled is overly long for CMS v3.5.x and should be corrected in later versions.

Turbo Sprockets

If assets compilation is slow, considering using the Turbo Sprockets gem which avoid recompiling assets that didn't change. Follow the installation instructions for the gem.

If you run into permission errors during deploy, it may be related to file permissions that requires ownership of files to retimestamp them. Try adding the following to capistrano gem to take ownership of the assets directory.

namespace :deploy do
  namespace :assets do
    task :take_ownership do
      run "ME=`whoami` && sudo chown -R $ME /path/to/assets/directory"
    end
    task :return_ownership do
     run "sudo chown -R rightful_owner /path/to/assets/directory"
    end
  end
end
before "deploy:assets:precompile", "deploy:assets:take_ownership"
after "deploy:assets:precompile", "deploy:assets:return_ownership"

Configuring bundler

For Rails 3/bundler, you need to tell capistrano to run bundle install after deployment. See "Bundler's Docs":http://gembundler.com/rationale.html and "this post":http://blog.josephholsten.com/2010/09/deploying-with-bundler-and-capistrano/ for some ideas into how to do that. The most common setup will be something like this though:

# In config/deploy.rb
require "bundler/capistrano

# This may not be necessary, depending on your server setup.
# To see other options, run cap --explain bundle:install
set :bundle_cmd, '/opt/ruby-ee/bin/bundle'

Using database editable templates

In production, if you want to use the editable templates feature (Admin -> Templates), you will need to tell Rails not to cache views. Otherwise, changes to templates will not be picked up until you restart Passenger.

# In config/environments/production.rb
config.action_view.cache_template_loading = false