Setup a Ruby on Rails, Passenger, Nginx running on an AWS EC2 with RDS.

Here is a slimline walkthrough for getting a complete setup and working AWS EC2 Ruby on Rails instance. For this walkthrough we will be using rails version 2.3.15 and using bundler for the gems. We will also be using an RDS instance which requires knowing your root user's password.

Setup:

First add user for our deployments (via capistrano)

sudo adduser deploy  

Now we work out what we need to be installed:

nginx, passenger, ruby 1.8.17, rails 2.3.15, bundler, mysql (as a client), capistrano

I am using a quite 'outdated' rails version compared to what is now available. But the installation procedure should be similar if not exactly the same.

Make sure your system is up to date:

apt-get update  
apt-get upgrade  
# configured hostname
sudo hostname railsweb01.somecompany.com  

For a Localsystem (Non RDS):

# grant privileges to user running capistrano
GRANT ALL PRIVILEGES, GRANT OPTION ON *.* TO ‘deploy’@’localhost’ IDENTIFIED BY ‘<pass>’;  
GRANT GRANT OPTION ON *.* TO ‘deploy’@’localhost’ IDENTIFIED BY ‘<pass>’;  

For RDS

mysql> grant all on '%'.* to deploy@'%' IDENTIFIED BY '<pass>';  
Query OK, 0 rows affected (0.01 sec)  
mysql> grant grant option on '%'.* TO deploy@'%';  
Query OK, 0 rows affected (0.00 sec)  

Now we need to install some dependencies / some nice tools for when we actually want to use the system.

sudo apt-get -y install build-essential zlib1g-dev libssl-dev libreadline-dev libyaml-dev libcurl4-openssl-dev curl git-core python-software-properties sqlite3 libsqlite3-dev imagemagick libmagickwand-dev libmysqlclient-dev mysql-client zsh  

Now comes the very daunting task of installing rvm. The ruby version management system. This makes it seriously easy to configure multiple ruby versions!

RVM:

curl -L https://get.rvm.io | sudo bash -s stable

# add users to use ruby to rvm group
usermod -a -G rvm deploy

# logout and log back into for new group to take effect. 

rvm pkg install openssl  
rvm reinstall all --force

# rvm will install patches for this version of ruby
# here is where you'd change your version numbers for more up-to-date versions
rvm install ruby-1.8.7-p371 

# use specific gemset with set rails version
rvm use ruby-1.8.7-p371  
rvm gemset create rails2.3.15  
rvm gemset use rails2.3.15 --default  
gem list

# Add to .bashrc/.zshrc so RVM enviornment variables get loaded in a non interactive environment
# must be above “case $- in”

[[ -s "/usr/local/rvm/scripts/rvm" ]] && . "/usr/local/rvm/scripts/rvm" # This loads RVM into a shell session.

Slowbro used tinker, Ruby install was successful!

Nginx with passenger:

# install passenger & use passenger-nginx module to install nginx for us (compiled from source).
gem install passenger  
sudo mkdir /opt/nginx/  
sudo chown ubuntu /opt/nginx  
passenger-install-nginx-module

# install the init scripts to start nginx
wget -O init-deb.sh http://library.linode.com/assets/660-init-deb.sh  
sudo mv 660-init-deb.sh /etc/init.d/nginx  
sudo chmod +x /etc/init.d/nginx  
sudo /usr/sbin/update-rc.d -f nginx defaults

/etc/init.d/nginx start

Slowbro used defend. Nginx with passenger module install successful!

Install the required Gems:

# install the gems
(run rake gems:install to install ryanb-acts-as-list)

gem install sqlite3  
gem install rmagick  
# Set our default ruby&rails combo
rvm --default use ruby-1.8.7@rails2.3.15

# Capitstrano installation
gem install capistrano

# Now bundler
gem install bundler

# These are the only two gems I install globally, as they will be required for all of my ruby applications
# As in my opinion, its better to use bundler to download the required gems per app instead of having every gem under the sun installed on the server. 
# Trust me on that, as we had to migrate from that exact scenario... wasnt fun...

PHP-FPM: Optional but highly recommended:

# Install of php
sudo apt-get install php5-cli php5-common php5-suhosin  
# after the above install 
sudo apt-get install php5-fpm php5-cgi  

You are now finished. Congratulate yourself on installing Nginx with passenger, Ruby On Rails, on an EC2 instance. But there's always the configuration step! Woooot!

Configuring:

Configure Nginx:

cat /opt/nginx/nginx.conf:

user  deploy;  
worker_processes  4;

error_log  logs/error.log;  
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;

events {  
    worker_connections  1024;
}

http {  
    # Enable below if you have more than 128 hostnames on the server.
    #server_names_hash_bucket_size 128;
    # Enable below if your hostnames are longer than 100 characters.
    #server_names_hash_max_size 100;
    passenger_max_pool_size 200;
    #passenger_max_instances_per_app 7;
    rails_app_spawner_idle_time 600;
    passenger_pool_idle_time 300;
    passenger_debug_log_file /opt/nginx/logs/passenger-error.log;
    passenger_log_level 2;

    # Add your own gems, and wrapper locations. 
    passenger_root /usr/local/rvm/gems/ruby-1.8.7-p371@rails2.3.15/gems/passenger-3.0.19;
    passenger_ruby /usr/local/rvm/wrappers/ruby-1.8.7-p371@rails2.3.15/ruby;

    client_max_body_size 15M;

    include       mime.types;
    log_format main     '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';
    default_type  application/octet-stream;
    #access_log  logs/access.log  main;
    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  20;
    gzip  on;

    server {
        listen       80;
        server_name  localhost;
        error_page 403 /403.html;
        error_page 404 /404.html;
        #access_log  logs/host.access.log  main;
        location / {
            # If is bad, you shouldn't use this
            if (-f $document_root/system/maintenance.html) {
                return 503;
            }
            root   html;
            index  index.html index.htm index.php;
        }

#  Parse all .php file in the /var/www directory
    location ~ .php$ {
        fastcgi_split_path_info ^(.+.php)(.*)$;
        # This "backend" corresponds to the upstream "backend" below
        fastcgi_pass   backend;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_param  QUERY_STRING     $query_string;
        fastcgi_param  REQUEST_METHOD   $request_method;
        fastcgi_param  CONTENT_TYPE     $content_type;
        fastcgi_param  CONTENT_LENGTH   $content_length;
        fastcgi_intercept_errors        on;
        fastcgi_ignore_client_abort     off;
        fastcgi_connect_timeout 60;
        fastcgi_send_timeout 180;
        fastcgi_read_timeout 180;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 4 256k;
        fastcgi_busy_buffers_size 256k;
        fastcgi_temp_file_write_size 256k;
    }   

#        Disable viewing .htaccess and .htpassword
    location ~ /.ht {
        deny  all;
    }   
 }  
upstream backend {  
        # This socket location is corresponding to the php-pool configuration below
        server unix:/var/run/php5-fpm.sock;

}

Configure PHP-FPM:

cat /etc/php5/fpm/pool.d/www.conf

[www]
user = deploy  
group = deploy  
# Listen socket must equal the same as nginx
listen = /var/run/php5-fpm.sock  
pm = dynamic  
pm.max_children = 5  
pm.start_servers = 2  
pm.min_spare_servers = 1  
pm.max_spare_servers = 3  
chdir =/  

Second last add your nginx virtual host configuration. Remember that every configuration change that you make needs to be reloaded into nginx. But quick a word from the wise:

Everytime you reload nginx, every passenger application gets restarted. Which means every site goes down. In a virtual sites hosting environment this can be bad, so make sure you reload with caution!

Example from /opt/nginx/site-enabled/domain.com:

      server {
        server_name domain.com.au;
        rewrite ^(.*) http://www.domain.com.au$1 permanent;
      }
      server  {
        listen 80;
        server_name www.domain.com.au;
        root "/home/deploy/apps/domain_com_au/current/public";
        passenger_enabled on;
        rails_env production;

        # Set custom access/error logs for awstats
        access_log  /opt/nginx/logs/domain.com.au-access.log;
        error_log  /opt/nginx/logs/domain.com.au-error.log;

        # Configure /cgi-bin/scripts to go through php-fastcgi
        location ~ ^/cgi-bin/.*.(cgi|pl|py|rb) {
            gzip off;
            fastcgi_pass  backend;
            fastcgi_index cgi-bin.php;
            fastcgi_param SCRIPT_FILENAME    /opt/nginx/cgi-bin.php;
            fastcgi_param SCRIPT_NAME        /cgi-bin/cgi-bin.php;
            fastcgi_param X_SCRIPT_FILENAME  /usr/lib$fastcgi_script_name;
            fastcgi_param X_SCRIPT_NAME      $fastcgi_script_name;
            fastcgi_param QUERY_STRING       $query_string;
            fastcgi_param REQUEST_METHOD     $request_method;
            fastcgi_param CONTENT_TYPE       $content_type;
            fastcgi_param CONTENT_LENGTH     $content_length;
            fastcgi_param GATEWAY_INTERFACE  CGI/1.1;
            fastcgi_param REQUEST_URI        $request_uri;
            fastcgi_param DOCUMENT_URI       $document_uri;
            fastcgi_param DOCUMENT_ROOT      $document_root;
            fastcgi_param SERVER_PROTOCOL    $server_protocol;
            fastcgi_param REMOTE_ADDR        $remote_addr;
            fastcgi_param REMOTE_PORT        $remote_port;
            fastcgi_param SERVER_ADDR        $server_addr;
            fastcgi_param SERVER_PORT        $server_port;
            fastcgi_param SERVER_NAME        $server_name;
            fastcgi_param REMOTE_USER        $remote_user;
        }
      }
(of course you would/should set this up through a Capistrano recipe, so it automates it for you)

Now we increase our file descriptors. This is a work with passenger and nginx. As they use sockets, every new process creates a new socket, thus increasing our load on file descriptors. This is only really needed if you host around 100+ sites, and have an average of 50 active at one time. But if you don't change your file descriptors you will get a lot of 502 nginx errors and 500 errors.

vim /etc/security/limits.conf  
deploy soft nofile 4096  
deploy hard nofile 10240  

Now all you have to do is push your site live and your good to go!
If enough people want / I get time, I'll go though changing a rails application to one that can use AWS in a Production and Staging environment.

Tim Coombs

Administrator of Slowb.ro and world leader of my own mind, the only place our ideas and thoughts are our own in a world gone mad

In a terminal https://slowb.ro

Subscribe to Slowb.ro's Blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS