Skip to content

Instantly share code, notes, and snippets.

@benvds
Last active August 29, 2015 14:03
Show Gist options
  • Save benvds/93fe26464712fdaf9f0b to your computer and use it in GitHub Desktop.
Save benvds/93fe26464712fdaf9f0b to your computer and use it in GitHub Desktop.
Step by step guide to installing a Ruby on Rails application on Ubuntu

Step by step guide to installing a Ruby on Rails application on Ubuntu

This guide aims to get you started running a basic Ruby on Rails application on Ubuntu. It can be used for web applications with small usage and provides a cheap alternative, e.g. €5 a month on Digital Ocean, to services like Heroku which cost much more because of expensive add-ons. The setup includes:

  • support for just a single Ruby on Rails web application
  • database support (PostgreSQL)
  • email support (sendmail)
  • background job support
  • multiple application processes (Unicorn)
  • front-end http server with asset caching (Nginx)
  • basic process management setup

Later on I will also be adding instructions for:

  • deployment with git hooks
  • SSL certificates
  • automated backups
  • monitoring

We'll get the application running as quickly as possible and then expand on that. The guide assumes basic linux command line knowdledge on things like permissions, package management, symbolic links, job control, editor usage and more.

Additional information on setting up Ubuntu as a server can be found in the Ubuntu Server Guide.

1. Basic system setup

For this demo I've created a droplet on Digital Ocean, you can use whatever vps you like.

Create the droplet and save the ip address:

Hostname: demo_app.example.com
Image: Ubuntu 14.04 x64
Ip address: 93.184.216.119

Setup your ssh key.

Login as root:

> ssh root@93.184.216.119

change the password:

> passwd

Update & upgrade the system:

> sudo apt-get update && sudo apt-get upgrade

Add a user:

> adduser demo_user

After this I got the following warning:

perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
  LANGUAGE = (unset),
  LC_ALL = (unset),
  LC_CTYPE = "nl_NL.UTF-8",
  LANG = "en_US.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

We need to generate & reconfigure the locales to fix this:

> locale-gen nl_NL.UTF-8
> dpkg-reconfigure locales

(VRAAG: dit kan op verschillende manieren, dit zorgt ervoor dat je de sudoers file niet hoeft aan te passen maar is dit wel een net alternatief?) Add the user to the sudo-ers list so we don't need to login as root anymore:

> usermod -a -G sudo demo_user

We need to add the user to ssh and make it a bit more secure. You can update the config at /etc/ssh/sshd_config. Update the following line:

PermitRootLogin yes

to:

PermitRootLogin no

Add the new user at the end of the file, save the file and quit:

AllowUsers demo_user

Reload ssh to apply the new settings:

> reload ssh

Let's login with our newly created user. Quit the ssh connection (ctrl-d), add you public key and login as our new user:

> cat ~/.ssh/id_rsa.pub | ssh demo_user@93.184.216.119 "mkdir ~/.ssh; cat >> ~/.ssh/authorized_keys"
> ssh demo_user@93.184.216.119

Ubuntu comes with UFW which stands for Uncomplicated FireWall. It provides as a friendly way to configure the underlaying iptables configuration. We only need to enable ssh, http and https:

> sudo ufw allow ssh
> sudo ufw allow http
> sudo ufw enable

2. Installing ruby

Because we only have one app we also only need just one ruby version. We'll use Postmodern's ruby-install for that. It allows for installing specific ruby versions system wide in the /opt/rubies/ directory.

Building ruby and gems requires some libraries. Let's install them first:

> sudo apt-get install autoconf bison build-essential libssl-dev libyaml-dev libreadline6 libreadline6-dev zlib1g zlib1g-dev git

Then install ruby-install:

> wget -O ruby-install-0.4.3.tar.gz https://github.com/postmodern/ruby-install/archive/v0.4.3.tar.gz
> tar -xzvf ruby-install-0.4.3.tar.gz
> cd ruby-install-0.4.3/
> sudo make install

Install the ruby version you need:

> sudo ruby-install ruby 2.1.2

Create symbolic links for the ruby binaries

> sudo ln -s /opt/rubies/ruby-2.1.2/bin/* /usr/local/bin

Install the Bundler gem:

> sudo gem install bundler

Create symbolic links for the bundler binaries

> sudo ln -s /opt/rubies/ruby-2.1.2/bin/bundle* /usr/local/bin

3. Installing PostgreSQL

To install PostgreSQL as your database server:

> sudo apt-get install postgresql

Because we're going to install the pg gem, we have some additional dependencies:

> sudo apt-get install postgresql-server-dev-9.3

(TESTEN: niet meer nodig na install van postgresql-server-dev?) Let's build the gem:

> sudo gem install pg -- --with-pg-config=/usr/bin/pg_config

(VRAAG: wat is eigenlijk best practice op productie servers; md5 of ident?) Postgres' default authentication is using ident, but we'll use md5 instead, you can update the config at /etc/postgresql/9.3/main/pg_hba.conf:

# "local" is for Unix domain socket connections only
local   all             all                                     md5

Restart the database server

> sudo service postgresql restart

Run psql as the postgres user, create the user and database:

> sudo -u postgres psql
postgres=# CREATE USER apps2user WITH PASSWORD 'demo_password';
postgres=# CREATE DATABASE apps2_production OWNER apps2user;

4. Getting our app to work

We'll place our app directly in the users home directory. Other possible directories are /var/www which is common but not entirely correct and /srv which is for "site-specific data which is served by this system.". But for ease of use we'll stick with the home directory.

Checkout de demo app:

> cd ~ && git clone https://github.com/benvds/demo_app.git

Sprockets needs a javascript runtime, so lets install node:

> sudo apt-get install nodejs

Install gems into vendor:

> bundle install --path vendor

Migrate the database

> RAILS_ENV=production DB_PASSWORD=demo_password bin/rake db:migrate

Test drive the app:

> RAILS_ENV=production DB_PASSWORD=demo_password SECRET_KEY_BASE=demo_secret bin/rails server

Let's see it run (asset's won't work yet):

http://demo_app.example.com:3000

5. Nginx as reverse proxy

Let's get those assets to work and use Nginx as reverse proxy and for caching assets.

Install Nginx:

> sudo apt-get install nginx

Configure Nginx:

> sudo nano /etc/nginx/sites-available/default

Replace all with:

# /etc/nginx/sites-available/default

upstream webrick {
    server 127.0.0.1:3000 fail_timeout=0;
}

server {
    listen 80;
    server_name apps2.pomodoro.nl;

    # Application root, as defined previously
    root /home/demo_user/demo_app/public;

    location ^~ /assets/  {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
    }

    try_files $uri/index.html $uri @webrick;

    location @webrick{
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://webrick;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

And restart the server:

> sudo service nginx restart

Precompile the assets:

> bin/rake assets:precompile

Lets run the app again:

> RAILS_ENV=production DB_PASSWORD=demo_password SECRET_KEY_BASE=demo_secret bin/rails server

Our app is now available on port 80 and assets are available and cached:

http://demo_app.example.com

When things go wrong configuring nginx you'll see this in the command line:

Restarting nginx nginx        [fail]

This means there is something wrong with the configuration. You can check the log for details at /var/log/nginx/error.log.

6. Unicorn as application server

Webrick is not a production server so we're going to use unicorn for this. It's a popular app server mainly because of it's unixy approach. It takes some more time to setup compared to an all-in solution like Phusion Passenger but people seem to get better results with unicorn for single app servers.

The repository comes with an unicorn configuration file at config/unicorn.rb which is largely based on the default example. Our configuration looks like this:

app_name = "demo_app"
app_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))

# Only necessary when the app dir gets changed on new releases, see:
# http://www.justinappears.com/blog/2-no-downtime-deploys-with-unicorn/

# Unicorn::HttpServer::START_CTX[0] = "#{app_root}/bin/unicorn"
#
# before_exec do |server|
#   ENV["BUNDLE_GEMFILE"] = "#{app_root}/Gemfile"
# end

working_directory app_root

pid "#{app_root}/tmp/pids/unicorn.pid"
stderr_path "#{app_root}/log/unicorn.log"
stdout_path "#{app_root}/log/unicorn.log"

listen "/tmp/unicorn.#{app_name}.sock"

worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout Integer(ENV.fetch('WEB_TIMEOUT', 20))

preload_app true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!

  # This allows a new master process to incrementally
  # phase out the old master process with SIGTTOU to avoid a
  # thundering herd (especially in the "preload_app false" case)
  # when doing a transparent upgrade.  The last worker spawned
  # will then kill off the old master process with a SIGQUIT.
  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

Kill the rails server (ctrl-c) and let's use unicorn instead:

> RAILS_ENV=production DB_PASSWORD=demo_password SECRET_KEY_BASE=demo_secret bundle exec unicorn -p 5000 -c config/unicorn.rb

Let's put it in the background for now by stopping the process (ctrl-z) and resuming it in the background:

> bg $1

Update our nginx config at etc/nginx/sites-available/default, with:

# /etc/nginx/sites-available/default

upstream unicorn {
    server unix:/tmp/unicorn.demo_app.sock fail_timeout=0;
}

server {
    listen 80;
    server_name apps2.pomodoro.nl;

    # Application root, as defined previously
    root /home/demo_user/demo_app/public;

    location ^~ /assets/  {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
    }

    try_files $uri/index.html $uri @unicorn;

    location @unicorn{
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://unicorn;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 4G;
    keepalive_timeout 10;
}

Update the upstream server is now using a socket to connect to the unicorn master process. Restart ngnix:

> sudo service nginx restart

When things go wrong configuring unicorn you can find the log file at log/unicorn.log:

If you want it's possible to optimize your unicorn workers.

7. Sending emails & background jobs

(VRAAG: sendmail installeren lijkt voldoende te zijn maar online lees ik bijna overall dat ik postfix moet installeren, waarom?)

> sudo apt-get install sendmail

Were using sendmail but install postfix. The installation process will ask you two questions. Choose "internet site" (default) and set your domain name.

> sudo apt-get install postfix

The production environment needs te be configured to use sendmail. This is done in config/environments/production.rb:

config.action_mailer.delivery_method = :sendmail

Sending emails shouldn't delay an app, therefore should but put in a background job. The demo app is using Collective Idea's Delayed Job. It can be run with:

> RAILS_ENV=production DB_PASSWORD=demo_password SECRET_KEY_BASE=demo_secret bin/rake jobs:work

8. Environment variables & process management

Instead of starting all these processes and putting them in the background manually you'll need somethings better. Also setting environment variables through the command line is a hassle, we're gonna automate this with some process management.

We'll use Foreman for generating service scripts for our application server and background worker. It's uses a .env file for the environment variables en generates scripts for Ubuntu's Upstart which will make sure services restart whenever one crashes or the system reboots.

Foreman should be installed system wide so:

> sudo gem install foreman

And create symbolic link for the foreman binary

> sudo ln -s /opt/rubies/ruby-2.1.2/bin/foreman /usr/local/bin

Let's start by setting our environment variables in the .env file:

RAILS_ENV=production
DB_PASSWORD=demo_password
SECRET_KEY_BASE=

Let's generate a secret key base:

> bin/rake secret

Copy the generated secret and add it as SECRET_KEY_BASE to the .env file.

Our processes are defined in the Procfile:

web: bundle exec unicorn -c ./config/unicorn.rb
worker: bundle exec rake jobs:work

Check if it works by running:

> foreman start

If so, export the procfile to upstart:

> sudo foreman export upstart /etc/init -a demo_app -u demo_user

And start it up:

> sudo service demo_app start
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment