Ruby on Rails deployment
Various different ways to deploy a Ruby on Rails application. Assumes the server is set up and the databases are created.
Note that config/database.yml
, config/initializers/secret_token.rb
, and config/deploy.rb
aren't in the the Git repo.
One-time setup: create the deploy user
- On the server where the production systems will be deployed, create a new
deploy
user and lock the account
root@server:~# adduser deploy root@server:~# passwd -l deploy
Add the deploy user to the list of users who can SSH into the server. Add deploy to the AllowUsers
line in /etc/ssh/sshd_config
AllowUsers user1 user2 user3 git deploy
- the restart sshd:
root@server:~# service ssh restart
- Import the public keys from all users that need to trigger a deployment.
root@server:~# sudo -u deploy -i deploy@server:~$ scp user@desktop:.ssh/id_rsa.pub user.pub deploy@server:~$ cat user.pub >> .ssh/authorized_keys deploy@server:~$ rm user.pub
user@desktop
should now be able to log intodeploy@server
without a password.
- Generate a public key for
deploy@server
:
deploy@server:~$ ssh-keygen -t rsa
- Go back to wherever the Gitolite admin repository is. Add the
deploy
user's public key to the gitolite admin repo.
user@desktop:/gitolite-admin$ scp deploy@server:.ssh/id_rsa.pub keydir/deploy.pub
- In
conf/gitolite.conf
, add deploy
as a read-only user for the repos you want them to deploy, e.g.
repo sample
RW+ = user
R = daemon deploy
desc = Sample Rails application
- Commit and push the changes.
user@desktop:/gitolite-admin$ git add .
user@desktop:/gitolite-admin$ git commit -a -m "Added deploy user"
user@desktop:/gitolite-admin$ git push
- Back as the
deploy
user, check that you can clone a repo. (This also allows you to pass SSH's host authenticity test.)
deploy@ogedei:~$ git clone git@git.domain.tld:sample.git
- Make sure the
deploy
user can use RVM. Add the following to /home/deploy/.bashrc
:
if [ -f /etc/profile.d/rvm.sh ]; then
source /etc/profile.d/rvm.sh
fi
- Check
deploy
is using the correct Ruby version
deploy@server:~$ source /etc/profile.d/rvm.sh
deploy@server:~$ rvm list
rvm rubies
=* ruby-2.1.0 [ x86_64 ]
# => - current
# =* - current && default
# * - default
deploy@server:~$ which ruby
/usr/local/rvm/rubies/ruby-2.1.0/bin/ruby
Per-project setup
These are the steps needed to set up a project for the initial deployment.
Capify the project
- On the development machine, initialise Capistrano:
user@desktop:~/project$ cap install STAGES=production
- (
STAGES=production
omits the default "staging" deploy target.)
- Edit
project/config/deploy/production.rb
role :app, %w{deploy@server.domain.tld}
role :web, %w{deploy@server.domain.tld}
role :db, %w{deploy@server.domain.tld}, primary: true
- and ensure the "exteneded server syntax" line further down the file is commented out.
- Edit
project/config/deploy.rb
set :application, 'project'
set :repo_url, 'git@git.domain.tld:project.git'
set :branch, 'master'
set :default_stage, 'production'
set :deploy_to, '/var/www/project.domain.tld'
set :scm, :git
set :format, :pretty
# set :log_level, :debug
# set :pty, true
set :linked_files, %w{config/database.yml config/initializers/secret_token.rb}
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}
set :default_env, { path: "/opt/ruby/bin:$PATH" }
set :keep_releases, 5
namespace :deploy do
desc 'Restart application'
task :restart do
on roles(:app), in: :sequence, wait: 5 do
execute :touch, release_path.join('tmp/restart.txt')
end
end
after :restart, :clear_cache do
on roles(:web), in: :groups, limit: 3, wait: 10 do
# Here we can do anything such as:
# within release_path do
# execute :rake, 'cache:clear'
# end
end
end
after :finishing, 'deploy:cleanup'
end
- Edit
Capfile
require 'capistrano/rvm'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
Create the Apache2 virtual host
- Find out the correct path to use for the ruby you want (assumed to to be Ruby 2.1.0 here):
root@ogedei:~# rvm use 2.1.0
root@ogedei:~# passenger-config --ruby-command
passenger-config was invoked through the following Ruby interpreter:
Command: /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby
Version: ruby 2.1.0p0 (2013-12-25 revision 44422) [x86_64-linux]
To use in Apache: PassengerRuby /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby
To use in Nginx : passenger_ruby /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby
To use with Standalone: /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby /usr/bin/passenger start
- and place the
PassengerRuby
path in the Apache2 config below.
- Create the site virtual host file for Apache2. Create the file
/etc/apache2/site-available/project.domain.tld
:
VirtualHost *:80>
ServerName project.domain.tld
DocumentRoot /var/www/project.domain.tld/current/public
PassengerRuby /usr/local/rvm/gems/ruby-2.1.0/wrappers/ruby
<Directory /var/www/project.domain.tld/current/public/>
Allow from all
Options -MultiViews
</Directory>
LogLevel warn
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/project.access.log combined
ServerSignature Off
</VirtualHost>
- Create the directories and give them the correct permissions:
deploy
need to write to them, www-data
needs to read them:
root@ogedei:~# mkdir -p /var/www/project.domain.tld/public
root@ogedei:~# mkdir -p /var/www/project.domain.tld/config
root@ogedei:~# chown -R deploy:www-data /var/www/project.domain.tld
root@ogedei:~# ls -lah /var/www/project.domain.tld/
/var/www/project.domain.tld/:
total 16
drwxr-xr-x 4 deploy www-data 4.0K Jan 10 20:35 .
drwxr-xr-x 13 root root 4.0K Jan 10 20:35 ..
drwxr-xr-x 2 deploy www-data 4.0K Jan 10 20:35 config
drwxr-xr-x 2 deploy www-data 4.0K Jan 10 20:35 public
Create the production database
- On the server, create the (empty) database
root@server:~# sudo -u postgres psql postgres
could not change directory to "/root"
psql (9.1.11)
Type "help" for help.
postgres=# create role projectuser with createdb login password 'projectpassword';
CREATE ROLE
postgres=# create database projectdb owner=projectuser;
CREATE DATABASE
postgres=# \q
- Add the production database password to
config/database.yml
.
Make the first deployment
- Run the deployment check:
user@desktop:~project$ bundle exec cap production deploy:check
- which will create additional directories on the server.
- Manually copy across the
conig/database.yml
file to the server:
user@desktop:~project$ scp config/database.yml deploy@server:/var/www/project.domain.tld/shared/config/
- Rerun the deployment check:
user@desktop:~project$ bundle exec cap production deploy:check
- and you should get a nice green list of successes.
- Deploy the project!
user@desktop:~project$ bundle exec cap production deploy
Subsequent deployments
They're as simple as the first deployment:
user@desktop:~project$ bundle exec cap production deploy
See also
- Capistrano installation
- Capistrano readme
- Capistrano tasks (also available with the command
bundle exec cap -T
- Phusion Passenger user guide
- Maintenance page: a Capistrano maintenance gem and some with Apache2 config for using it.
- An old but still useful guide to using Capistrano on a single box
- How to simplify Capistrano output