
Deploying a Ruby on Rails application on Scaleway
- compute
- server
- Ruby
- Rails
- Scaleway-Elements
- nginx
- rbenv
- puma
- PostgreSQL
- systemd
- vps
Ruby on Rails - Overview
Ruby on Rails is a server-side web application framework written in Ruby and released under the MIT License.
The popularity of Ruby on Rails emerged in the years 2000 and was greatly influenced by web application development. The framework encourages and facilitates the use of common web standards such as JSON, HTML, CSS and JavaScript for user interfacing.
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.
In this tutorial you will learn basic steps to configure a deployment-ready machine using Capistrano, rbenv, Puma as application server and Nginx as a reverse proxy. We assume that you have already a working Ruby on Rails application and deploy it using the following stack on a Instance:
- nginx as the reverse proxy
- PostgreSQL as the database
- puma for the Ruby concurrent web server
- rbenv for managing several Ruby versions
- Capistrano as deployment utility
- Ubuntu 20.04 as operating system and Systemd for keeping processes up.
You may need certain IAM permissions to carry out some actions described on this page. This means:
- you are the Owner of the Scaleway Organization in which the actions will be carried out, or
- you are an IAM user of the Organization, with a policy granting you the necessary permission sets
- You have an account and are logged into the Scaleway console
- You have configured your SSH Key
- You have an Instance or Elastic Metal server running on Ubuntu Focal Fossa (20.04 LTS).
- You have sudo privileges or access to the root user.
Installing rbenv and nodejs
Ruby on rails needs to have a ruby runtime to run. It could be tempting to just install the ruby version provided by the distribution. The problem with that is that this version might be different from the one we currently need. Because we want to have the liberty to choose the version of ruby we want, we will install a dedicated ruby version manager called rbenv.
This version will also come with a modern ruby installation, but you are free to change it to fit your needs.
- The installation is done using the
apt
package manager of Ubuntu. Update theapt
package cache prior to installation, to make sure to install the latest available version in the Ubuntu repository:root@instance:~# apt updateroot@instance:~# apt install -y rbenv - Verify that the installation of
rbenv
andruby
was successful:root@instance:~# rbenv --versionrbenv 1.1.1root@instance:~# ruby --versionruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux-gnu]
In this tutorial we will use the default version of ruby but you are free to download yours to fit your needs. In case you need a more recent version of rbenv, instructions can be found on the official installer page.
Installing the nodejs runtime
Ruby on Rails requires having nodejs installed to compile certain web assets.
If you do not need a particular version of nodejs
for your deployment you can use the default one provided by the operating system.
- Install
nodejs
using theapt
package manager:root@instance:~# apt install nodejs - To check if the installation has been successful, run the
nodejs --version
command. The output should look similar to this:root@instance:~# nodejs --versionv10.19.0
Creating a non-admin user
Create a non-administrative user that will be able to run programs without having administrative privileges.
We will call this user deploy
.
- Create the user by running the following command as root:
root@instance:~# adduser deploy
- To check that the user creation was successful, log into the user account using the
su
command and check the installedruby
version:root@instance:~# su - deploydeploy@instance:~$ ruby --versionruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux-gnu]
Installing and configuring of PostgreSQL
Ruby on rails application typically use a SQL database for data persistence. In this step we will install all the dependencies required to have a database running.
-
Install the default PostgreSQL provided by the operating system using the
apt
package manager.root@instance:~# apt install postgresql postgresql-contrib libpq-dev -
Check if the installation was successful, by checking the status of the
postgresql
service:root@instance:~# service postgresql status● postgresql.service - PostgreSQL RDBMSLoaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)Active: active (exited) since Fri 2020-08-28 13:52:53 UTC; 27s agoMain PID: 843 (code=exited, status=0/SUCCESS)Tasks: 0 (limit: 614)Memory: 0BCGroup: /system.slice/postgresql.serviceAug 28 13:52:53 scw-vigilant-brown systemd[1]: Starting PostgreSQL RDBMS...Aug 28 13:52:53 scw-vigilant-brown systemd[1]: Finished PostgreSQL RDBMS. -
Create an unprivileged role and database so that rails can connect to the database. ```
Call this role
deploy
, just like our username on the instance: -
Use
su
to enter commands aspostgres
user, which is the default administrator of PostgreSQL:root@instance:~# su - postgres -
Once logged as the postgres user, create a database for the user
deploy
.Enter a strong password, when prompted. It will be your database password.
postgres@rails:~$ createuser --pwprompt deployEnter password for new role:Enter it again:postgres@instance:~$ createdb -O deploy my_rails_apppostgres@instance:~$ exit -
Once this is done, you can check that it worked by using the following command:
root@instance:~# psql --user deploy -h localhost -d my_rails_appPassword for user deploy:psql (12.4 (Ubuntu 12.4-0ubuntu0.20.04.1))SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)Type "help" for help.my_rails_app=>
Above is the shell of the database, ready to receive queries. Exit it by typing \q
.
Installing nginx and puma
Now we need to add a reverse proxy and a web server to our deployment.
There are two parts: The first part will be the reverse proxy which is called nginx and will be the process of receiving all the incoming HTTP traffic.
Then it will route this traffic to one or several applicative server.
The second step is the applicative server.
It is a ruby gem called puma that will run the code. puma is provided by default in the recent version of rails. So installing the dependencies of our project will install puma
too.
-
Install the nginx server using the following command via the
apt
package manager:root@instance:~# apt install nginx -
Configure nginx to pass all the traffic to the relevant ruby server by creating a file called
/etc/nginx/sites-available/my_rails_app
. Open it in a text editor and enter the following content:nano /etc/nginx/sites-available/my_rails_appupstream my_rails_app {# This socket file is created by your rails applicationserver unix:///home/vagrant/my_rails_app/tmp/puma.sock;}server {listen 80;server_name my_rails_app.com;# ~2 seconds is often enough for most folks to parse HTML/CSS and# retrieve needed images/icons/frames, connections are cheap in# nginx so increasing this is generally safe...keepalive_timeout 5;# path for static filesroot /home/deploy/my_rails_app/public;# this rewrites all the requests to the maintenance.html# page if it exists in the doc root. This is for capistrano's# disable web taskif (-f $document_root/maintenance.html) {rewrite ^(.*)$ /maintenance.html last;break;}location / {proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header Host $http_host;# If the file exists as a static file serve it directly without# running all the other rewrite tests on itif (-f $request_filename) {break;}# check for index.html for directory index# if it's there on the filesystem then rewrite# the url to add /index.html to the end of it# and then break to send it to the next config rules.if (-f $request_filename/index.html) {rewrite (.*) $1/index.html break;}# this is the meat of the rack page caching config# it adds .html to the end of the url and then checks# the filesystem for that file. If it exists, then we# rewrite the url to have explicit .html on the end# and then send it on its way to the next config rule.# if there is no file on the fs then it sets all the# necessary headers and proxies to our upstream pumasif (-f $request_filename.html) {rewrite (.*) $1.html break;}if (!-f $request_filename) {proxy_pass http://my_rails_app;break;}}# This should work as it gets the filenames with query strings that Rails provides.# However, there's a chance it could break the ajax calls.location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ {expires max;break;}# Error pages# error_page 500 502 503 504 /500.html;location = /500.html {root /home/deploy/my_rails_app/public;}}Save the file and exit the text editor.
-
Create a symbolic link to enable the server block:
ln -s /etc/nginx/sites-available/my_rails_app /etc/nginx/sites-enabled/ -
Check the configuration file for syntax errors using the following command:
root@instance:~# nginx -T -
Reload the nginx service to activate the new configuration
root@instance:~# service nginx reload
Configuring systemd to manage the puma process
We do not yet have a puma
server running.
Once this server runs, a socket will show up on /home/vagrant/my_rails_app/tmp/puma.sock
.
nginx
will forward the incoming request to this socket and puma
will answer over it.
To ensure that puma
is running and is restarted in case of a crash we will use a systemd configuration file:
- Create a new service file and open it in a text editor:
root@instance:~# nano /etc/systemd/system/puma.service
- Copy the following content into the file:
[Unit]Description=Puma HTTP ServerAfter=network.target# Uncomment for socket activation (see below)# Requires=puma.socket[Service]# Foreground process (do not use --daemon in ExecStart or config.rb)Type=simple# Preferably configure a non-privileged userUser=vagrant# The path to the your application code root directory.# Also replace the "<YOUR_APP_PATH>" place holders below with this path.# Example /home/username/myappWorkingDirectory=/home/vagrant/my_rails_app# Helpful for debugging socket activation, etc.# Environment=PUMA_DEBUG=1# SystemD will not run puma even if it is in your path. You must specify# an absolute URL to puma. For example /usr/local/bin/puma# Alternatively, create a binstub with `bundle binstubs puma --path ./sbin` in the WorkingDirectoryExecStart=/home/vagrant/.rbenv/shims/puma -C ./config/puma.rbRestart=always[Install]WantedBy=multi-user.target
- Save the file and exit the text editor.
- Enable the service so it can be started automatically during system boot:
root@instance:~# systemctl status puma.service
- Start the service manually:
root@instance:~# systemctl start puma.service
- Check if the service is working:
root@instance:~# systemctl status puma.service
Deploying my code on the server
The next step will be to deploy your local code. For that we will use Capistrano.
- In the
Gemfile
of your rails application add the following gems:gem 'capistrano', '~> 3.11'gem 'capistrano-rails', '~> 1.4'gem 'capistrano-rbenv', '~> 2.1', '>= 2.1.4' - To push a new version of your code, run the following command:
cap production deploy
Going further
In this tutorial we only installed basic dependencies but there is many other services that could be installed: elasticsearch, redis, …
All the steps made in this tutorial could be made on a local virtual machine.