As Ruby on Rails developers we get used to hosting our apps on platforms such as Heroku, which make configuration and deployment fairly easy. Sometimes, however, we want to have more control over server configuration and all of the steps of deployment, backup or monitoring. VPS providers like AWS EC2 or DigitalOcean droplets come in handy in such cases. Maintaining those may be not as simple as in case of Heroku, but there are few automation tools written in ruby (e.g. Capistrano, Chef) that make this task easier.
This article describes basic steps to set up deployment-ready VPS instance using Capistrano with RVM, Puma as application server and Nginx as reverse proxy. Let’s assume we already have working rails application, hosted on remote git repository. For sake of this article I’ve chosen “t2.micro” EC2 instance with “Ubuntu Server 14.10 LTS”. Let’s set it up.
Set up the server
At this point you should know IP address of the server and have private key stored on your local machine. It’s a file generated on creation of the instance and required to connect to it for the first time. Log into the server using SSH:
$ ssh -i "keypair.pem" ubuntu@XXX.XXX.XXX.XXX
Update existing packages on your new server:
$ sudo apt-get update && sudo apt-get -y upgrade
Now, in order to use capistrano to deploy your app to this server you need to do some preparation.
First, create user named deploy
, with its home directory and set password for it.
$ sudo useradd -d /home/deploy -m deploy
$ sudo passwd deploy
Add deploy
to sudo privileged user by rinning sudo visudo
. Paste following line to opened file and save.
deploy ALL=(ALL:ALL) ALL
The deploy
will need to access your git repository for deployment.
Generate a key pair for him then, but do not set the passphrase:
$ su - deploy
$ ssh-keygen
Add newly generated public key (.ssh/id_rsa.pub
) to your repository’s deployment keys.
Capistrano uses deploy
account for deployment and connects to it by SSH.
To make this possible, copy public key from your local machine and add
it to authorized keys of deploy
user. On server, edit this file:
$ vim .ssh/authorized_keys
Finally, install Git which is required for automated deployment with Capistrano:
$ sudo apt-get install git
Nginx
We will use Nginx as reverse proxy. It will redirect HTTP requests to the Puma application server through a UNIX socket. Let’s install it:
$ sudo apt-get install nginx
and configure default site. Open the site config file:
$ sudo vim /etc/nginx/sites-available/default
comment out existing content and paste the following into the file:
upstream app {
# Path to Puma SOCK file, as defined previously
server unix:/home/deploy/my-app/shared/tmp/sockets/puma.sock fail_timeout=0;
}
server {
listen 80;
server_name localhost;
root /home/deploy/my-app/current/public;
try_files $uri/index.html $uri @app;
location / {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_pass http://app;
}
location ~ ^/(assets|fonts|system)/|favicon.ico|robots.txt {
gzip_static on;
expires max;
add_header Cache-Control public;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 4G;
keepalive_timeout 10;
}
Set up the database
You need to set up a database for your app. If you are using PostgreSQL, here are steps:
$ sudo apt-get install postgresql postgresql-contrib libpq-dev
Create database user (‘myapp’ in this example):
$ sudo -u postgres createuser -s myapp
Log into PostgreSQL console:
$ sudo -u postgres psql
And change the password for newly created user:
postgres=# \password myapp
Confirm and exit console with \q
. Next, create the database:
$ sudo -u postgres createdb -O myapp my-app_production
RVM and Ruby
If you are not, login again to deploy
user’s shell.
Run following lines to install RVM:
$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
$ \curl -sSL https://get.rvm.io | bash -s stable
This will install RVM into deploy
user’s home directory.
Logout and log in again to load into the shell.
Now, install ruby using rvm.
You can install latest stable version:
$ rvm install ruby
or specific one:
$ rvm install 2.2.3
After it’s installed switch to installed version:
$ rvm use 2.2.3
Also install bundler gem:
$ gem install bundler --no-ri --no-rdoc
Capistrano prerequisites
Before first deployment we have to create directories and files to store database settings and environment specific data that are required by Capistrano.
$ mkdir my-app
$ mkdir -p my-app/shared/config
$ vim my-app/shared/config/database.yml
Paste the following in database.yml and save:
production:
adapter: postgresql
encoding: unicode
database: my-app_production
username: myapp
password: mypassword
host: localhost
port: 5432
Next, create application.yml
$ vim my-app/shared/config/application.yml
and add the following line:
SECRET_KEY_BASE: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
But first go to your local machine and run the rake secret
command to generate new secret to replace the one above.
Node.js
One final thing before we finish setting up the server. Rails use Node.js as Javascript runtime to compile assets, so you might need to install it:
$ curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -
$ sudo apt-get install -y nodejs
Now, let’s configure app itself. go back to your local machine.
Deploying Rails App with Capistrano
Capistrano lets you configure set of tasks needed to perfom deploymentof your applications to remote servers and run it automatically. First, add following gems to to your Gemfile:
gem 'figaro'
gem 'puma'
group :development do
gem 'capistrano'
gem 'capistrano3-puma'
gem 'capistrano-rails', require: false
gem 'capistrano-bundler', require: false
gem 'capistrano-rvm'
end
And install them with bundler:
$ bundle install
Now, to configure capistrano deployments you need to generate proper config files by running following command:
$ cap install STAGES=production
It will create config/deploy.rb file which is main configuration file and config/deploy/production.rb which contains environment specific settings, such as server IP, username, etc.
It also generates Capfile in the root of application, where you
require dependencies that you need to accomplish task. Add following
lines to Capfile after require 'capistrano/deploy'
:
require 'capistrano/bundler'
require 'capistrano/rvm'
require 'capistrano/rails/assets' # for asset handling add
require 'capistrano/rails/migrations' # for running migrations
require 'capistrano/puma'
Edit deploy.rb as follows:
lock '3.4.0'
set :application, 'my-app'
set :repo_url, 'git@github.org:mnieborak/my-app.git' # Edit this to match your repository
set :branch, :master
set :deploy_to, '/home/deploy/my-app'
set :pty, true
set :linked_files, %w{config/database.yml config/application.yml}
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads}
set :keep_releases, 5
set :rvm_type, :user
set :rvm_ruby_version, 'ruby-2.2.3' # Edit this to match ruby version you use
set :puma_rackup, -> { File.join(current_path, 'config.ru') }
set :puma_state, "#{shared_path}/tmp/pids/puma.state"
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock" #accept array for multi-bind
set :puma_conf, "#{shared_path}/puma.rb"
set :puma_access_log, "#{shared_path}/log/puma_error.log"
set :puma_error_log, "#{shared_path}/log/puma_access.log"
set :puma_role, :app
set :puma_env, fetch(:rack_env, fetch(:rails_env, 'production'))
set :puma_threads, [0, 8]
set :puma_workers, 0
set :puma_worker_timeout, nil
set :puma_init_active_record, true
set :puma_preload_app, false
Of course change my-app to name of your app.
To set the server IP open config/deploy/production.rb and paste the following line. Change IP address to your server’s IP:
server 'XXX.XXX.XXX.XXX', user: 'deploy', roles: %w{web app db}
As you might notice in Gemfile, we are also using Figaro gem.
It lets you set environment-specific configuration using single YAML file.
You can run $ figaro install
which creates config/application.yml and adds it to .gitignore or you can do it by hand.
Everything is ready to start first deployment using Capistrano:
$ cap production deploy
On the first deployment Capistrano will create hierarchy of directories and files that it uses to organise the source code and other deployment-related data. Then it will deploy the app, migrate database and start Puma server. Finally login to your server again and restart nginx service to reload new configuration:
$ sudo service nginx restart
Now, you can visit your app in browser using your server’s IP address. You should see your application up and running.
Summary
Now, you should know basic “how to” of deploying Rails application to VPS using Capistrano and Nginx. But as your app grows you can use Capistrano configuration and tasks to accomplish much more. I encourage you to further reading, as there are many more subjects concerning deployment and maintenace of Rails apps on VPSes.
Thank you, and looking forward for comments :)
Post by Marcin Nieborak
Marcin joined our team in 2013. He specializes in Ruby, JavaScript and Elixir.
as