This is the second half of a two-part tutorial. Missed the first part? Visit Part 1: Setting up a VPS with rbenv and Ruby 2.3.0.
Now that the VPS is set up, how about we use it to run an honest-to-goodness Rails app?
Capistrano is a great tool for deploying Rails, and it will work perfectly well with an Ubuntu 14.04 VPS running rbenv. For this example we’ll use the Puma app server, since it is simple to set up and has exceptionally good Capistrano integration.
We’ll also use sqlite3 for the database. Normally you wouldn’t use sqlite3 in production, but it’s the simplest option and helps keep this tutorial short and to the point.
Capistrano works from your local computer, using SSH to remotely control your VPS. Run all these steps on your local development machine unless otherwise specified.
1 Create a new Rails app
rails new blog -d sqlite3
This creates a new Rails app called “blog” using sqlite3 as the database. Feel free to name the app whatever you want, or use an existing app.
In order to deploy the app, you’ll also need to push it to a git repository, like Bitbucket or GitHub. This step is outside the scope of this tutorial. I’ll assume you are familiar with git.
2 Install your SSH private key on the VPS
ssh-copy-id deployer@<your VPS IP address>
Capistrano relies on your private key to work its SSH magic. Make sure your private key is installed in the deployer user on your VPS. You need to be able to SSH to the VPS without being prompted for a password.
Mac users: get the ssh-copy-id command with
brew install ssh-copy-id.
3 Add Capistrano and Puma gems
gem "capistrano-rails", :group => :development gem "capistrano3-puma"
Add these gems to your
Gemfile, and then:
bundle exec cap install STAGES=production
This Capistrano command prepares your Rails project for deployment by generating all the necessary Capistrano files:
Capfile(loads Capistrano plugins)
config/deploy.rb(configures the deployment recipes for your app)
config/deploy/production.rb(environment-specific server settings)
5 Load the Rails and Puma plugins for Capistrano
At the bottom of
Capfile, uncomment/add the following lines:
require "capistrano/bundler" require "capistrano/rails/assets" require "capistrano/rails/migrations" require "capistrano/puma"
This tells Capistrano that we’d like to load the “recipes” for deploying a Rails app using bundler and Puma. That means we don’t have to write any code of our own! With a few config settings it will work out of the box.
You do not need the
6 Configure the deploy recipes
config/deploy.rb (you can delete the rest of the sample deploy.rb that Capistrano generated):
set :application, "blog" set :repo_url, "<your git@ bitbucket/github URL goes here>" set :linked_dirs, %w( bin log vendor/bundle public/system tmp/pids tmp/cache tmp/sockets ) set :puma_bind, "tcp://0.0.0.0:8080"
Capistrano will deploy your app by fetching your app directly from the master branch of the git repository (e.g. Github or Bitbucket). The
repo_url must be correct or Capistrano will fail early on in the deployment process. Make sure your latest code is pushed to
Puma requires some tmp directories to be in place; the
linked_dirs setting ensures these are set up.
puma_bind tells Puma to publish our app on TCP port
8080. Normally Puma connects to Nginx on a UNIX domain socket, but I’m skipping Nginx to keep this tutorial short. Instead, Puma will serve web requests directly on port 8080.
7 Tell Capistrano about your VPS
config/deploy/production.rb (again, you can delete the rest of the generated sample config):
server "<your VPS IP address goes here>", :user => "deployer", :roles => %w(web app db)
Here’s where we tell Capistrano how to SSH into the VPS, and what portions of the app are hosted there. Since we only have one server, it necessarily is responsible for all roles:
%w(web app db).
8 Specify the Rails secret key
In production, Rails expects a
SECRET_KEY_BASE environment variable to be present. This key is used to encrypt cookies and keep your application safe from attackers.
We’ll make use of another rbenv plugin to set this environment variable. As the
deployer user on your VPS, run:
echo "SECRET_KEY_BASE=34d69b7015b267fbd5efd91241c9ad1d7d08bd55f2b157558098426e0f4bb2e163a6899f90dc35ca58d5821ec16dac55bed151939eca80463111872499c86ae7" > ~/.rbenv/vars
The rbenv-vars plugin automatically loads the contents of the
.rbenv/vars file into the environment whenever any Ruby process is executed. This includes Rails, so
SECRET_KEY_BASE will get passed via the environment to our app. Easy!
(In practice you should’t use my secret key; generate your own by running
9 Create the deployment directory
root on the VPS:
sudo mkdir -p /var/www/blog sudo chown deployer /var/www/blog
Capistrano will deploy your app to
/var/www/<application> by default, where the application name is defined by
set :application in
deploy.rb. Deployment will fail if this directory doesn’t exist or is not writable by the deployer user.
10 Copy the Puma config to the VPS
Back on your local machine, run:
bundle exec cap production deploy:check bundle exec cap production puma:config
deploy:check command builds out the necessary directory structure. Then
puma:config generates a
puma.rb configuration file and places it in the correct location on the VPS. We’re in the final stretch!
11 Deploy the app!
bundle exec cap production deploy
It may take a few minutes to complete the first deploy, due to the
bundle install step, which needs to download all the necessary gems and compile native extensions.
But if all goes well, the app will soon be up an running on port 8080 of your VPS IP address. And don’t panic: a brand new Rails app with no customization will show this message:
The page you were looking for doesn’t exist.
That’s because you have a blank app with no routes, controllers, or views. Generate some, commit and push those to git, then run the deploy command again to see the results.
Deploying a Rails app to your own VPS is not trivial, but with tools like Capistrano and tutorials like this one, I hope you agree that it is not rocket science.
If you have a knack for Linux system administration, or just a desire to own and control the complete deployment stack and avoid vendor lock-in, I encourage you to give it a shot.