15.3. Webserver Without Deployment

Please log in as root on the web server.

Warning

When using this installation guide for other Rails projects, please note that you need to replace the name "blog" (for the relevant Rails application) with the appropriate project name in all configuration files and scripts.

Setting Up New Rails Project

To keep this guide as simple as possible, we create a simple blog directly on the server as user deployer.
root@debian:~# su - deployer
deployer@debian:~$ rails new blog
[...]
deployer@debian:~$ cd blog
deployer@debian:~/blog$ rails generate scaffold post subject content:text
[...]
deployer@debian:~/blog$

Adapting Gemfile

Please write the following content into the file Gemfile:
source 'https://rubygems.org'

gem 'rails', '3.2.6'

gem 'sqlite3'

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'

  # See https://github.com/sstephenson/execjs#readme for more supported runtimes
  # gem 'therubyracer', :platforms => :ruby

  gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'

# To use ActiveModel has_secure_password
# gem 'bcrypt-ruby', '~> 3.0.0'

group :production do
  # Use MySQL as the production database
  gem 'mysql'

  # Use unicorn as the app server
  gem 'unicorn'
end
Then install all gems with bundle install:
deployer@debian:~/blog$ bundle install
[...]
deployer@debian:~/blog$

Production Database Configuration

In the file config/database.yml you need to enter the database configuration for the MySQL database for the production system. Please make sure you enter the correct password.
# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: mysql
  encoding: utf8
  database: blog
  username: deployer
  password: YourFavoritePassword

Unicorn Configuration

For the Unicorn configuration, we use the file https://raw.github.com/defunkt/unicorn/master/examples/unicorn.conf.rb as basis and save it as follows in the file config/unicorn.rb after we adapt it to our server:
# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
worker_processes 4

# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
user "deployer", "www-data"

# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
APP_PATH = "/var/www/blog/current"
working_directory APP_PATH

# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
listen "/tmp/unicorn.blog.sock", :backlog => 64
listen 8080, :tcp_nopush => true

# nuke workers after 30 seconds instead of 60 seconds (the default)
timeout 30

# feel free to point this anywhere accessible on the filesystem
pid APP_PATH + "/tmp/pids/unicorn.pid"

# By default, the Unicorn logger will write to stderr.
# Additionally, some applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
stderr_path APP_PATH + "/log/unicorn.blog.stderr.log"
stdout_path APP_PATH + "/log/unicorn.blog.stdout.log"

before_fork do |server, worker|
  # the following is highly recomended for Rails + "preload_app true"
  # as there's no need for the master process to hold a connection
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end

  # Before forking, kill the master process that belongs to the .oldbin PID.
  # This enables 0 downtime deploys.
  old_pid = "/tmp/unicorn.my_site.pid.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  # the following is *required* for Rails + "preload_app true",
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end

  # if preload_app is true, then you may also want to check and
  # restart any other shared sockets/descriptors such as Memcached,
  # and Redis.  TokyoCabinet file handles are safe to reuse
  # between any number of forked children (assuming your kernel
  # correctly implements pread()/pwrite() system calls)
end

before_exec do |server|
  ENV["BUNDLE_GEMFILE"] = "/var/www/blog/current/Gemfile"
end

rake db:migration

We still need to create the database:
deployer@debian:~/blog$ rake db:migrate RAILS_ENV=production
[...]
deployer@debian:~/blog$ 

Important

Please ensure that the rake db:migrate concludes with a RAILS_ENV=production. This is to migrate the production database.

rake assets:precompile

rake assets:precompile ensures that all assets in the asset pipeline are made available for the production environment (see Chapter 12, Asset Pipeline).
deployer@debian:~/blog$ rake assets:precompile
/home/deployer/.rvm/rubies/ruby-1.9.3-p194/bin/ruby /home/deployer/.rvm/gems/ruby-1.9.3-p194@global/bin/rake assets:precompile:all RAILS_ENV=production RAILS_GROUPS=assets
deployer@debian:~/blog$

Unicorn Init Script

Now you need to continue working as user root:
deployer@debian:~$ exit
Abgemeldet
root@debian:~# 
Create the init script /etc/init.d/unicorn_blog with the following content:
#!/bin/bash

### BEGIN INIT INFO
# Provides:          unicorn
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: Unicorn webserver
# Description:       Unicorn webserver for the blog
### END INIT INFO

UNICORN=/home/deployer/.rvm/bin/bootup_unicorn
UNICORN_ARGS="-D -c /home/deployer/blog/config/unicorn.rb -E production"
KILL=/bin/kill
PID=/home/deployer/tmp/unicorn.pid

sig () {
  test -s "$PID" && kill -$1 `cat $PID`
}

case "$1" in
        start)
                echo "Starting unicorn..."
                $UNICORN $UNICORN_ARGS
                ;;
        stop)
                sig QUIT && exit 0
                echo >&2 "Not running"
                ;;
        restart)
                $0 stop
                $0 start
                ;;
        status)
                ;;
        *)
                echo "Usage: $0 {start|stop|restart|status}"
                ;;
esac
You still have to activate the init script and start Unicorn:
root@debian:~# chmod +x /etc/init.d/unicorn_blog 
root@debian:~# update-rc.d -f unicorn_blog defaults
update-rc.d: using dependency based boot sequencing
root@debian:~# /etc/init.d/unicorn_blog start
root@debian:~# 
Your Rails project is now accessible via the IP address of the web server.

nginx Configuration

For the Rails project, we add a new configuration file /etc/nginx/conf.d/blog.conf with the following content:
upstream unicorn {
  server unix:/tmp/unicorn.blog.sock fail_timeout=0;
}

server {
  listen 80 default deferred;
  # server_name example.com;
  root /home/deployer/blog/public;

  location / {
    gzip_static on;
  }

  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;
}
We rename the default configuration file to make sure it is no longer executed. Then we restart nginx.
root@debian:~# mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.backup
root@debian:~# /etc/init.d/nginx restart
Restarting nginx: nginx.
root@debian:~#

Loading New Version of Rails Project

As you can see from this heading, we do not have automatic deployment mechanisms in this variation. But you can easily implement automatic deployment by using a script.
If you want to activate changes to the Rails project, you need to log in as user deployer and use rake assets:precompile after a code update to ensure that all assets in the asset pipeline are made available once more for the production environment (see Chapter 12, Asset Pipeline).
deployer@debian:~/blog$ rake assets:precompile
/home/deployer/.rvm/rubies/ruby-1.9.3-p194/bin/ruby /home/deployer/.rvm/gems/ruby-1.9.3-p194@global/bin/rake assets:precompile:all RAILS_ENV=production RAILS_GROUPS=assets
deployer@debian:~/blog$
If you bring in new migrations, you of course also need to do a rake db:migrate RAILS_ENV=production:
deployer@debian:~/blog$ rake db:migrate RAILS_ENV=production
[...]
deployer@debian:~/blog$ 
Then you need to restart Unicorn as user root:
root@debian:~# /etc/init.d/unicorn_blog restart
root@debian:~# 

Tip

In most cases, it makes more sense to use an installation with Capistrano deployment. Initially, this involves a tiny bit more effort (see Section 15.4, “Web Server with Capistrano Deployment”), but then it offers greater comfort and security.

Updates about this book will be published on my Twitter feed.