Introduction
Deploying a Ruby on Rails application involves a number of steps, such as copying source code files, running database migrations, pre or post-compiling assets and restarting the web server. Our goal as developer should be to automate this procedure down to a single command we can run at any time.
There are tools that can help with this. In this tutorial, you will learn how to configure a deployment tool called Capistrano, which essentially can run arbitrary task on a remote machine over SSH.
Server Requirements
A typical Rails application server has the following components:
- 1 GB of RAM or more
- A modern version of Ruby (2.0+)
- Bundler and Git installed
- Apache or Nginx configured as reverse proxy
- A non-root user with write permissions to your target deploy directory (in the rest of the article we will assume that this user is called “deployer”).
In this tutorial we will assume that you have such a server already available.
Installing Capistrano
Let’s start by installing Capistrano. To do so, add the following lines to your Gemfile
and run bundle install
:
gem 'capistrano'
gem 'capistrano-rails'
gem 'capistrano3-puma'
gem 'puma'
If your server is using RVM, add gem 'capistrano-rvm'
. For rbenv users, there is capistrano-rbenv
. If you prefer to use another server other than Puma, you should substitute the 'capistrano3-puma'
and 'puma'
gems with those corresponding to your server of choice. Capistrano’s GitHub profile contains a list of available gems.
Once Capistrano is installed, run the following command inside your project directory:
bundle exec cap install
This will generate a few example configuration files.
You should find the example files generated by Capistrano under config/deploy
. Both files (staging.rb
and production.rb
) have the same contents. It’s a good idea to take a look at them to familiarize yourself with the Capistrano configuration format.
Here is a listing of the generated files in a tree format:
|-- Capfile
|-- config
| |-- deploy
| | |-- production.rb
| | `-- staging.rb
| `-- deploy.rb
`-- lib
`-- capistrano
`-- tasks
Before we can continue to create our own configuration, we will have to generate an SSH key-pair for passwordless authentication.
Generating SHH Keys
SSH allows you to use a pair of cryptographic keys for authentication instead of a password. Programs can use these keys to log into your server to automate tasks. To generate a new pair of keys, run the following command:
ssh-keygen
The program will ask you for the location where the key pair is going to be saved. The default is fine, unless you already have a pair of keys created. It will also ask for a password – you should leave it blank.
semaphoreci@ubuntu:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/semaphoreci/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/semaphoreci/.ssh/id_rsa.
Your public key has been saved in /home/semaphoreci/.ssh/id_rsa.pub.
The key fingerprint is:
c4:b8:ec:f5:5d:27:97:f0:5b:46:58:e3:4a:f5:87:64 semaphoreci@ubuntu
The key's randomart image is:
+--[ RSA 2048]----+
| Eo.|
| o oo+o|
| . o oo.+|
| . o . +.o|
| o S .oo=|
| . . . . . =o|
| . . . . |
| |
| |
+-----------------+
The key pair is composed of a public key and a private key. You should never share your private key, as that would give access to your server to anyone who has it.
Before you can actually log in, you will have to install the public key into your app server. To do that, copy the contents of the ~/.ssh/id_rsa.pub
(local) file into /home/deployer/.ssh/authorized_keys
(on the server). Don’t forget to substitute “deployer” with the actual user on your server.
Alternatively, you can use the ssh-copy-id script to do this step for you.
# Only do this if you haven't done the manual step of copying the key
ssh-copy-id <user>@<your-server-ip>
Once that’s done, you should be able to SSH manually into your server without using a password to verify that everything is working as expected.
ssh <user>@<your-server-ip>
Welcome to Ubuntu 14.04.2 LTS (GNU/Linux 3.11.0-20-generic x86_64)
If you would like a more complete explanation on how SSH works, please refer to the SSH getting started guide on Semaphore Community.
Configuring Capistrano for Deployment
We are now ready to write our configuration. Open config/deploy.rb
in your favorite editor and pay attention to the following lines:
set :application, 'my_app_name'
set :repo_url, 'git@example.com:me/my_repo.git'
set :deploy_to, '/var/www/my_app_name'
These are pretty self-explanatory – you should modify them to suit your case. For example, if your application name was ‘Shopping site’, the repository URL git@github.com:matugm/shopping-site.git
, and you wanted to host the application inside the /var/www/shopping-site
directory on your server, the configuration would look like this:
set :application, 'Shopping site'
set :repo_url, 'git@github.com:matugm/shopping-site.git'
set :deploy_to, '/var/www/shopping-site'
Cloning a Private Repository
If your repository is private, then you will have to generate another pair of SHH keys, but this time on your server. Make sure you do this as your deploy user.
If your repository is hosted on GitHub, copy the public SSH key and add it as a deploy key to your project This should enable your server to clone the repository. To verify, SSH as your deploy user to your server and run the git clone once manually:
deployer@example.com:~$ git clone git@github.com:matugm/shopping-site.git
Configuring Deployment to Production
Next, you need to edit config/deploy/production.rb
. In this file, you tell Capistrano your server’s domain or IP address, the user it should log in as, and the roles of this server. For a small app, one server should be able to manage all three roles. The roles are:
app
– the Rails application itselfdb
– the databaseweb
– the front-end server that will act as a proxy and serve static assets (e.g. nginx)
For large applications, you may want to consider separating the roles into different servers.
In our case, you should have the following line:
server 'example.com', user: 'deploy', roles: %w{app db web}
Finally, you will need to edit the Capfile
file and uncomment the additional tasks you want it to perform during deployment. For example, uncommenting the following lines will make it run bundler, pre-compile assets, run migrations and restart Puma.
require 'capistrano/bundler'
require 'capistrano/puma'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
Environment Variables
Rails 4 requires the secret key setting to be set. In production, it picks this value from the SECRET_KEY_BASE
environment variable. You should set this value in the /home/deployer/.bashrc
file on your server (preferably at the start of it).
To generate a secret for the first time, you run be bundle exec rake secret
inside your project directory.
Here is an example of setting your secret key in /home/deployer/.bashrc
:
export SECRET_KEY_BASE=8304hl047oy8743kjhfksdhf9734dsfjosdfu08434
Puma Setup
Your puma configuration file will be located on the server under <deploy-directory>/shared/puma.rb
. You will need to make some directories for it to store logs and temporary files. You will also need to replace with your actual directory (the same one you specified via :deploy_to
in Capistrano configuration). Remember that these files reside only on your server, so you won’t find them in your local project directory.
mkdir -p <deploy-directory>/shared/tmp/sockets/
mkdir -p <deploy-directory>/shared/tmp/pids
mkdir -p <deploy-directory>/shared/log
Running Your First Deploy
At this point, we should be able to deploy our app. Since you can have multiple deploy environments, we need to tell Capistrano which one we want to deploy to – in this case production (from config/deploy/production.rb
).
bundle exec cap production deploy
You will see a lot of output from Capistrano that explains what it is doing. If everything went right, your app should be up and running on the IP address or domain that you specified in production.rb
.
INFO [844e3cd0] Finished in 0.139 seconds with exit status 0 (successful).
DEBUG [7dae283d] Running /usr/bin/env [ -f /var/www/shopping-site/shared/puma.rb ] as root@46.101.29.162
DEBUG [7dae283d] Command: [ -f /var/www/shopping-site/shared/puma.rb ]
DEBUG [7dae283d] Finished in 0.106 seconds with exit status 1 (failed).
WARN puma.rb NOT FOUND!
DEBUG Uploading /var/www/shopping-site/shared/puma.rb 0.0%
INFO Uploading /var/www/shopping-site/shared/puma.rb 100.0%
INFO puma.rb generated
DEBUG [48ff8212] Running /usr/bin/env [ -f /var/www/shopping-site/current/REVISION ] as root@46.101.29.162
DEBUG [48ff8212] Command: [ -f /var/www/shopping-site/current/REVISION ]
DEBUG [48ff8212] Finished in 0.104 seconds with exit status 1 (failed).
DEBUG [651332d9] Running /usr/bin/env [ -f /var/www/shopping-site/repo/HEAD ] as root@46.101.29.162
DEBUG [651332d9] Command: [ -f /var/www/shopping-site/repo/HEAD ]
DEBUG [651332d9] Finished in 0.110 seconds with exit status 1 (failed).
DEBUG [299f1ee6] Running /usr/bin/env if test ! -d /var/www/shopping-site; then echo "Directory does not exist '/var/www/shopping-site'" 1>&2; false; fi as root@46.101.29.162
DEBUG [299f1ee6] Command: if test ! -d /var/www/shopping-site; then echo "Directory does not exist '/var/www/shopping-site'" 1>&2; false; fi
DEBUG [299f1ee6] Finished in 0.107 seconds with exit status 0 (successful).
INFO [c9d1bf2c] Running /usr/bin/env git clone --mirror https://github.com/matugm/shopping-site.git /var/www/shopping-site/repo as root@46.101.29.162
DEBUG [c9d1bf2c] Command: cd /var/www/shopping-site && ( GIT_ASKPASS=/bin/echo GIT_SSH=/tmp/Shopping-site/git-ssh.sh /usr/bin/env git clone --mirror https://github.com/matugm/shopping-site.git /var/www/shopping-site/repo )
DEBUG [c9d1bf2c] Cloning into bare repository '/var/www/shopping-site/repo'...
INFO [c9d1bf2c] Finished in 1.648 seconds with exit status 0 (successful).
DEBUG [1c3184be] Running /usr/bin/env if test ! -d /var/www/shopping-site/repo; then echo "Directory does not exist '/var/www/shopping-site/repo'" 1>&2; false; fi as root@46.101.29.162
DEBUG [1c3184be] Command: if test ! -d /var/www/shopping-site/repo; then echo "Directory does not exist '/var/www/shopping-site/repo'" 1>&2; false; fi
DEBUG [1c3184be] Finished in 0.130 seconds with exit status 0 (successful).
INFO [b52f660b] Running /usr/bin/env git remote update as root@46.101.29.162
DEBUG [b52f660b] Command: cd /var/www/shopping-site/repo && ( GIT_ASKPASS=/bin/echo GIT_SSH=/tmp/Shopping-site/git-ssh.sh /usr/bin/env git remote update )
DEBUG [b52f660b] Fetching origin
INFO [b52f660b] Finished in 0.855 seconds with exit status 0 (successful).
DEBUG [abe79a76] Running /usr/bin/env if test ! -d /var/www/shopping-site/repo; then echo "Directory does not exist '/var/www/shopping-site/repo'" 1>&2; false; fi as root@46.101.29.162
DEBUG [abe79a76] Command: if test ! -d /var/www/shopping-site/repo; then echo "Directory does not exist '/var/www/shopping-site/repo'" 1>&2; false; fi
DEBUG [abe79a76] Finished in 0.137 seconds with exit status 0 (successful).
INFO [4e14fc8b] Running /usr/bin/env mkdir -p /var/www/shopping-site/releases/20150509170957 as root@46.101.29.162
DEBUG [4e14fc8b] Command: cd /var/www/shopping-site/repo && ( GIT_ASKPASS=/bin/echo GIT_SSH=/tmp/Shopping-site/git-ssh.sh /usr/bin/env mkdir -p /var/www/shopping-site/releases/20150509170957 )
INFO [4e14fc8b] Finished in 0.141 seconds with exit status 0 (successful).
INFO [70ad5204] Running /usr/bin/env git archive master | tar -x -f - -C /var/www/shopping-site/releases/20150509170957 as root@46.101.29.162
DEBUG [70ad5204] Command: cd /var/www/shopping-site/repo && ( GIT_ASKPASS=/bin/echo GIT_SSH=/tmp/Shopping-site/git-ssh.sh /usr/bin/env git archive master | tar -x -f - -C /var/www/shopping-site/releases/20150509170957 )
Conclusion
In this article we demonstrated how to configure Capistrano to deploy a Rails application to a Puma server. You can find more information about Capistrano and its options on capistranorb.com. Once it’s configured, getting your code online is just one command away. Go ahead and start deploying, but don’t forget to run your tests first!
P.S. Would you like to learn how to build sustainable Rails apps and ship more often? We’ve recently published an ebook covering just that — “Rails Testing Handbook”. Learn more and download a free copy.