Setting up Rails 5 app deployment with capistrano in DO
First of all you have to create a new account in DigitalOcean, do it .
-
Create a new droplet
Before creating your new droplet you have to configure a SSH key, to do this go to Settings -> Security
In order to create a new SSH key in Linux or MacOs, open the console and run
ssh-keygen
, the console will ask for a passphrase, the ssh-key will be located in~/.ssh/id_rsa.pub
, if you already have this file just runcat ~/.ssh/id_rsa.pub
, copy the content and paste it inside the SSH Keys field in DigitalOcean.Create the droplet with the resources that you need, if you don't know how estimate how many resources will you app need, I recommend create the most basic posible, DigitalOcean give you the option for increase the memory and CPU of your droplets.
-
Configure your droplet
Log in to your server with the following command:
ssh root@droplet_ip
Inside the server you need to add a new user, never user the root user:
adduser name
The following command add the recent created user superuser properties:
usermod -aG sudo name
Log in with the new user:
su - name
Create this folder and files in order to log in from your local machine:
~: mkdir .ssh
~: nano ~/.ssh/authorized_keys
Inside the file
~/.ssh/authorized_keys
you have to paste the ssh-key of your local machine, to exit nano editor press ctrl+x, log out from the droplet:exit
From now on, you can access to your droplet putting in the console
ssh name@ip_del_droplet
-
Install Nginx
Nginx is the server that will be act as a proxy for our app.
sudo apt-get update
sudo apt-get install curl git-core nginx -y
-
Install RVM and Ruby
In the section when says
2.5.0
you have to put the ruby version of your app.gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
curl -sSL https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
rvm requirements
rvm install 2.5.0
rvm use 2.5.0 --default
-
Install Rails and Bundler
To handle the dependecies.
gem install rails -V --no-ri --no-rdoc
gem install bundler -V --no-ri --no-rdoc
-
Install Postgresql
In this case my app use Postgresql as DB, install it with the following commands:
sudo apt-get install postgresql postgresql-contrib
After the DB system is instaled, we proceed to create a new db and a user for it, I recommend to put the same name as the user we create for our droplet, and give it superuser permissions.
createuser --interactive
Create the db.
createdb app-production
To access to the psql console from our server, just do the following:
psql -U name -d app-production
-
Configure remote repository
In order that our server could obtain the code of the app is necesary configure it to have permission. For accomplish that you need to generate an ssh-key from your server, log in to your server and run:
ssh-keygen
cat ~/.ssh/id_rsa.pub
Copy the content and go to github under settings
and paste the ssh key of your server with write and read permissions.
Start the communication between your server and github with the following:
ssh -T git@github.com
-
Configure capistrano
Add the gems on the Gemfile.
group :development do gem 'capistrano', require: false gem 'capistrano-rvm', require: false gem 'capistrano-rails', require: false gem 'capistrano-bundler', require: false gem 'capistrano3-puma', require: false end
Install and run the command to generate the files that we need to use capistrano.
bundle install && cap install
This command creates the following files
Capfile
,config/deploy.rb
,config/deploy/production.rb
. Inside Capfile paste this:require "capistrano/setup" require "capistrano/deploy" require "capistrano/rails" require "capistrano/bundler" require "capistrano/rvm" require 'capistrano/puma' require "capistrano/scm/git" install_plugin Capistrano::SCM::Git install_plugin Capistrano::Puma Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
You have to put the following configuration inside config/deploy.rb
server 'droplet_ip', roles: [:web, :app, :db], primary: true set :application, 'app_name' set :repo_url, 'git@github.com:user/repo_name.git' set :user, 'username' set :puma_threads, [4,16] set :puma_workers, 0 set :pty, true set :use_sudo, false set :stage, :production set :deploy_via, :remote_cache set :deploy_to, "/home/#{fetch(:user)}/apps/#{fetch(:application)}" set :puma_bind, "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock" set :puma_state, "#{shared_path}/tmp/pids/puma.state" set :puma_pid, "#{shared_path}/tmp/pids/puma.pid" set :puma_access_log, "#{release_path}/log/puma.error.log" set :puma_error_log, "#{release_path}/log/puma.access.log" set :ssh_options, { forward_agent: true, user: fetch(:user), keys: %w(~/.ssh/id_rsa) } set :puma_preload_app, true set :puma_worker_timeout, nil set :puma_init_active_record, true # Change to false when not using ActiveRecord set :rvm_ruby_version, '2.5.0' set :branch, :master set :linked_files, %w{config/database.yml config/master.key} set :linked_dirs, %w{log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system public/uploads public/assets} namespace :puma do desc 'Create Directories for Puma Pids and Socket' task :make_dirs do on roles(:app) do execute "mkdir #{shared_path}/tmp/sockets -p" execute "mkdir #{shared_path}/tmp/pids -p" end end before :start, :make_dirs end namespace :deploy do desc "Make sure local git is in sync with remote." task :check_revision do on roles(:app) do unless `git rev-parse HEAD` == `git rev-parse github/master` puts "WARNING: HEAD is not the same as origin/master" puts "Run `git push` to sync changes." exit end end end desc 'Initial Deploy' task :initial do on roles(:app) do before 'deploy:restart', 'puma:start' invoke 'deploy' end end desc 'Restart application' task :restart do on roles(:app), in: :sequence, wait: 5 do invoke 'puma:restart' end end before :starting, :check_revision after :finishing, :compile_assets after :finishing, :cleanup end
Remember to change app_name for the name of your app, the ruby version and the name of the branch you want to deploy. Now paste the following inside config/deploy/production:
set :stage, :production set :rails_env, :production server 'ip_del_droplet', user: 'tu_usuario_en_el_servidor', roles: %w{web app db}, primary: true
Create a file inside config folder called nginx.conf, and paste the following:
upstream puma { server unix:///home/usuario/apps/app_name/shared/tmp/sockets/app_name-puma.sock; } server { listen 80 default_server deferred; # if you already have a domain, put it in the following line, if not just comment it server_name domain.com; root /home/username/apps/app_name/current/public; access_log /home/username/apps/app_name/current/log/nginx.access.log; error_log /home/username/apps/app_name/current/log/nginx.error.log info; location ^~ /assets/ { gzip_static on; expires max; add_header Cache-Control public; } try_files $uri/index.html $uri @puma; location @puma { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://puma; } error_page 500 502 503 504 /500.html; client_max_body_size 10M; keepalive_timeout 10; }
-
Installl ubuntu firewall (ufw)
As an extra security layer we will install the ubuntu firewall, for that we have to log in in our server.
ssh username@idroplet_ip
sudo ufw app list
We will see a list of apps, between we will find OpenSSH and Nginx, we have to allow to pass the firewall:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
With this we are done configuring the firewall, that will allow conections in the port that OpenSSH and Nginx needs.
-
Deploy your app
Save and upload the code we give you in step 8 in the remote repository.
git add .
git commit -m "Adding capistrano configuration"
git push remote branchname
Run the following:
cap production deploy:initial
This step will fail, don't worry is because we have to create some files. Create the file home/username/apps/app_name/shared/config/database.yml and write the information needed to connect with the DB, create home/username/apps/app_name/shared/config/master.key and put the master key that you generate to handle rails credentials.
It's possible that we need to install node in our server, For this do the following:
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
sudo apt-get install -y nodejs
Create a symbolic link for Nginx
sudo rm /etc/nginx/sites-enabled/default
sudo ln -nfs /home/username/apps/app_name/current/config/nginx.conf /etc/nginx/sites-enabled/app_name"
sudo service nginx restart
Deploy your app (this run it in your local machine):
cap production deploy
Now you can acces to your application from web browser through the droplet ip or from the domain that you configure in the nginx.conf file.
-
Configure SSL with Letsencrypt
Now to make your website secure we will configure the SSL protocol, for that we need to install the certbot, log in to your server and type the following:
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install python-certbot-nginx
In order to obtain the SSL certificate for our domains, we need to do the next:
sudo certbot --nginx -d example.com -d www.example.com
We have to put the domains we configure inside the nginx.conf file, if you have severals domains just add the flag
-d
following by the domain.Make sure that everything is working and to automatic renew our certificate run:
sudo certbot renew --dry-run
I hope this info has been useful to you, any doubts or error from my part just feel free to comment below.