Deploying a Ruby on Rails application on Scaleway
- Ruby-on-Rails
- nginx
- rbenv
- puma
- PostgreSQL
- systemd
- vps
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 year 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 a deployment-ready VPS Instance using Capistrano with rbenv, Puma as the application server, and Nginx as the reverse proxy. Let’s assume we already have a working Rails application, hosted on a 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 an 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 22.04 as operating system and Systemd for keeping processes up.
Before you start
To complete the actions presented below, you must have:
- A Scaleway account logged into the console
- Owner status or IAM permissions allowing you to perform actions in the intended Organization
- An SSH key
- An Instance or Elastic Metal server running on Ubuntu Jammy Jellyfish (22.04 LTS)
sudo
privileges or access to the root user
Installing rbenv and Node.js
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 before the installation to ensure you have the latest version available 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.2.0root@instance:~# ruby --versionruby 3.1.4p376 (2023-03-30 revision 0db68f0233) [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. If you need a more recent version of rbenv, instructions can be found on the official installer page.
Installing the Node.js 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
node --version
command. The output should look similar to this:root@instance:~# node --versionv18.16.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 3.1.4p376 (2023-03-30 revision 0db68f0233) [x86_64-linux-gnu]
Installing and configuring PostgreSQL
Ruby on Rails typically uses an SQL database for data persistence. In this section, 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 Tue 2024-07-16 10:23:54 UTC; 12s agoProcess: 5602 ExecStart=/bin/true (code=exited, status=0/SUCCESS)Main PID: 5602 (code=exited, status=0/SUCCESS)CPU: 1msJul 16 10:23:54 scw-gallant-neumann systemd[1]: Starting PostgreSQL RDBMS...Jul 16 10:23:54 scw-gallant-neumann 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 as thepostgres
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 (14.10 (Ubuntu 14.10-0ubuntu0.22.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
is nginx, a reverse proxy that will handle the incoming traffic. The second part is puma, a fast concurrent web server for Ruby and Rack.
Installing and configuring puma
Puma is the concurrent web server that will host your Ruby on Rails application.
To install it, first, add the puma
gem to your Rails application.
- Add
puma
to your Gemfile:gem "puma" - Install the gem:
deploy@instance:~$ bundle install
- To check that the installation was successful, run the following command:
deploy@instance:~$ puma -Vpuma version 6.0.1
Installing and configuring nginx
Install the latest version of nginx provided by the operating system:
- Update the list of available packages:
root@instance:~# apt update
- Install the
nginx
package:root@instance:~# apt install nginx - Check the status of the
nginx
service:root@instance:~# service nginx status● nginx.service - A high performance web server and a reverse proxy serverLoaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)Active: active (running) since Tue 2024-07-16 10:23:54 UTC; 1min 23s agoMain PID: 6002 (nginx)Tasks: 2 (limit: 2359)Memory: 2.6MCGroup: /system.slice/nginx.service├─6002 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;└─6003 nginx: worker processJul 16 10:23:54 scw-gallant-neumann systemd[1]: Starting A high performance web server and a reverse proxy server...Jul 16 10:23:54 scw-gallant-neumann systemd[1]: nginx.service: Failed to parse PID from file /run/nginx.pid: Invalid argumentJul 16 10:23:54 scw-gallant-neumann systemd[1]: Started A high performance web server and a reverse proxy server.
If everything looks good, let’s move on to the next section.
Configuring Capistrano for deployment
Capistrano is a deployment utility we will use to automate the process of publishing our Rails application. It will perform tasks such as migrating the database, installing dependencies, and managing restart services.
- Add the
capistrano
gem to your Gemfile:group :development dogem 'capistrano', '~> 3.16'gem 'capistrano-rbenv'gem 'capistrano-bundler'gem 'capistrano-rails'gem 'capistrano3-puma'end - Install the gem:
deploy@instance:~$ bundle install
- Set up Capistrano:
deploy@instance:~$ bundle exec cap install
- Edit the
Capfile
to use the required plugins:# Load DSL and Setup Up Stagesrequire 'capistrano/setup'# Include default deployment tasksrequire 'capistrano/deploy'# Include tasks from other gems included in your Gemfile## For documentation on these, see for example:## https://github.com/capistrano/rbenv# https://github.com/capistrano/bundler# https://github.com/capistrano/rails# https://github.com/capistrano/passengerrequire 'capistrano/rbenv'require 'capistrano/bundler'require 'capistrano/rails/assets'require 'capistrano/rails/migrations'require 'capistrano/puma'install_plugin Capistrano::Pumainstall_plugin Capistrano::Puma::Nginxinstall_plugin Capistrano::Puma::Systemd# Load custom tasks from `lib/capistrano/tasks` if you have any definedDir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } - Edit
config/deploy.rb
to configure the deployment:# config valid for current version and patch releases of Capistranolock "~> 3.16.0"set :application, "my_rails_app"set :repo_url, "git@example.com:me/my_repo.git"# Default branch is :master# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp# Default deploy_to directory is /var/www/my_app_nameset :deploy_to, "/var/www/my_rails_app"# Default value for :format is :airbrussh.# set :format, :airbrussh# You can configure the Airbrussh format using :format_options.# These are the defaults.# set :format_options, command_output: true, log_file: "log/capistrano.log", color: :auto, truncate: :auto# Default value for :pty is false# set :pty, true# Default value for :linked_files is []# append :linked_files, "config/database.yml"# Default value for linked_dirs is []# append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/system"# Default value for default_env is {}# set :default_env, { path: "/opt/ruby/bin:$PATH" }# Default value for local_user is ENV['USER']# set :local_user, -> { `git config user.name`.chomp }# Default value for keep_releases is 5# set :keep_releases, 5set :rbenv_type, :user # or :system, depends on your rbenv setupset :rbenv_ruby, '3.1.4'# set :rbenv_map_bins, %w{rake gem bundle ruby rails}set :rbenv_roles, :all # default value# Puma configurationsset :puma_threads, [4, 16]set :puma_workers, 0# Don't change these unless you know what you're doingset :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 :puma_preload_app, trueset :puma_worker_timeout, nilset :puma_init_active_record, true # Change to false when not using ActiveRecord - Configure the stages
config/deploy/production.rb
:server 'your.server.ip', user: 'deploy', roles: %w{app db web}set :ssh_options, {forward_agent: true,auth_methods: %w(publickey),keys: %w(~/.ssh/id_rsa)} - Create the necessary directories on the server:
deploy@instance:~$ mkdir -p /var/www/my_rails_app/shared/config
Setting up Systemd services
Systemd will keep our Puma application server running, restart it on crashes, and handle the logging.
- Create a systemd service file for Puma:
root@instance:~# nano /etc/systemd/system/puma.service
- Add the following content to the file:
[Unit]Description=Puma HTTP ServerAfter=network.target[Service]Type=simpleUser=deployWorkingDirectory=/var/www/my_rails_app/currentExecStart=/home/deploy/.rbenv/shims/bundle exec puma -C /var/www/my_rails_app/current/config/puma.rbExecReload=/bin/kill -TSTP $MAINPIDRestart=always[Install]WantedBy=multi-user.target
- Reload systemd to apply the changes:
root@instance:~# systemctl daemon-reload
- Enable the Puma service to start on boot:
root@instance:~# systemctl enable puma
Setting up nginx as a reverse proxy
Configure nginx to forward traffic to Puma.
- Create a new nginx site configuration:
root@instance:~# nano /etc/nginx/sites-available/my_rails_app
- Add the following content to the file:
server {listen 80;server_name your.server.ip;root /var/www/my_rails_app/current/public;location / {proxy_pass http://unix:/var/www/my_rails_app/shared/tmp/sockets/my_rails_app-puma.sock;proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Forwarded-Proto $scheme;}location ~ ^/(assets|packs) {expires max;gzip_static on;}error_page 500 502 503 504 /500.html;client_max_body_size 2M;keepalive_timeout 10;}
- Enable the site by creating a symlink:
root@instance:~# ln -s /etc/nginx/sites-available/my_rails_app /etc/nginx/sites-enabled/
- Test the nginx configuration:
root@instance:~# nginx -t
- Restart nginx to apply the changes:
root@instance:~# systemctl restart nginx
Deploying the application
Deploy the application using Capistrano.
- Deploy the application for the first time:
deploy@instance:~$ bundle exec cap production deploy
This command will check out the code from the repository, install the necessary gems, precompile assets, and restart the application server.
Your Rails application should now be running on your server, accessible via the IP address or domain name configured in the nginx setup.
Conclusion
With the above steps, you’ve successfully set up a deployment workflow for a Rails application using a Capistrano, Puma, and Nginx stack on a fresh Ubuntu server. This setup ensures a smooth deployment process and a robust, scalable server environment.