X

Deploy Ruby On Rails: Ubuntu 22.04 Jammy Jellyfish in 2024

The ultimate guide for deploying Ruby on Rails in a production environment on your Virtual Private Server (VPS).

Choosing a Server Hosting Provider
We will use Digital Ocean because it is affordable, fast and easy to use. Of course, you can explore other hosting provider options such as :
Linode
Vultr
Amazon EC2
Amazon LightSail
Packet
Google Cloud
Microsoft Azure

Determining the appropriate server size is crucial, with a primary focus on RAM, given the resource-intensive nature of Ruby and Rails applications. Additionally, running databases like Postgresql or MySQL, along with Redis for background workers, adds to the RAM requirements. For initial deployments, I suggest opting for a 2GB RAM server ($10/mo). While a 1GB server might be feasible, there's a likelihood of running into RAM constraints, especially during asset compilation in production deployment.

Initiate the server creation process by navigating to DigitalOcean and accessing the "Create Droplet" page.

Step 1: Select your operating system

Opt for Ubuntu 22.04 as the preferred operating system for your server. This version is a long-term support (LTS) release, ensuring extended security updates, a critical factor for production environments.

In the "Choose an image" section, pick Ubuntu 22.04 from the dropdown menu.


Step 2: Choose your size
Proceed to choose the server size based on your comfort level.

If uncertain, opt for a 2GB RAM server initially. The flexibility of virtual servers allows seamless resizing to a larger capacity later, without any data loss. This characteristic of "virtual" servers enables the addition of more RAM or CPUs at any point, providing convenient scalability.

Step 3: Select your region.

In this step, pick the server region, which determines the data centre location for your server. Opt for a region that is in close proximity to your users or yourself, ensuring optimal performance.

Step 4: Optional settings.

If desired, you can enable the following options:

Private Networking: This is beneficial if you intend to have a distinct database server or if you need to communicate with other servers in the datacenter.

IPv6: Enable this option to assign an IPv6 address to your server. It's a good practice, though not mandatory.

Monitoring: Activate this option to receive basic metrics on server usage. It provides useful insights into server performance.

Backups: This feature creates backups of your entire server as an image, allowing for restoration. While these backups may not occur frequently, they offer a safety net. Alternatively, you can skip these if you prioritize more frequent hourly database backups.

Step 5: Create your server.

Click on "Create" to initiate the server creation process. DigitalOcean will take approximately 60 seconds to set up your server. After completion, check your email for the password associated with the new server.

To log in as root, use the following command, replacing `1.2.3.4` with your server's public IP address:

ssh root@1.2.3.4


 Step 6: Creating a Deploy user.

While logged in as `root` on the server, execute the following commands to establish the deploy user and include them in the sudo group, limiting permissions in production to mitigate the risk of potential full server control in the event of a security breach:

root$ adduser deploy
root$ adduser deploy 
root$ sudo
root$ exit

Moving forward, let's streamline the login process by adding our SSH key to the server. We'll employ a tool called `ssh-copy-id` for this task.

ssh-copy-id root@1.2.3.4
ssh-copy-id deploy@1.2.3.4

You can now log in as either `root` or `deploy` without entering a password!

For the remainder of this tutorial, it's recommended to be logged in as deploy to set up everything. Let's SSH in as deploy now, and you shouldn't be prompted for a password this time.

ssh deploy@1.2.3.4

Installing Ruby
We're about to install Ruby using a Ruby version manager, which is beneficial not only in development but also in production for quick Ruby version upgrades.

Begin by installing dependencies required for Ruby compilation and some Rails dependencies. To ensure Webpacker support in Rails, we'll first add the Node.js and Yarn repositories to our system before installing them.

Additionally, we'll install Redis to enable ActionCable for websockets in production. You may also consider configuring Redis as your production store for caching.

Ensure you're logged in as the deploy user on the server and execute the following commands:

deploy$ # Adding Node.js repository
deploy$ curl -sL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
deploy$ # Adding Yarn repository
deploy$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
deploy$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
deploy$ sudo add-apt-repository ppa:chris-lea/redis-server
deploy$ # Refresh our packages list with the new repositories
deploy$ sudo apt-get update
# Install our dependencies for compiiling Ruby along with Node.js and Yarn
sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev dirmngr gnupg apt-transport-https ca-certificates redis-server redis-tools nodejs yarn


We can now begin installing Ruby since we have dependencies installed.

Choose the version of Ruby you want to install:
Moving forward, our next step is to install Ruby using a Ruby version manager called rbenv. It stands out as the easiest and simplest option, complemented by convenient plugins for straightforward management of environment variables in a production environment.

deploy$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
deploy$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
deploy$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
deploy$ git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
deploy$ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
deploy$ git clone https://github.com/rbenv/rbenv-vars.git ~/.rbenv/plugins/rbenv-vars
deploy$ exec $SHELL
deploy$ rbenv install 3.3.0
deploy$ rbenv global 3.3.0
deploy$ ruby -v
deploy$ # ruby 3.3.0

The last step is to install Bundler:

deploy$ # This installs the latest Bundler, currently 2.x.
deploy$ gem install bundler
deploy$ # For older apps that require Bundler 1.x, you can install it as well.
deploy$ gem install bundler -v 1.17.3
deploy$ # Test and make sure bundler is installed correctly, you should see a version number.
deploy$ bundle -v
deploy$ # Bundler version 2.0


If you encounter a message saying "bundle not found," execute the command rbenv rehash and then attempt the operation again.

Installing NGINX & Passenger

In our production environment, NGINX will serve as the web server for handling incoming HTTP requests. Subsequently, these requests will be directed to Passenger, responsible for running our Ruby application.

The installation process for Passenger is relatively uncomplicated. We will include their repository, followed by the installation and configuration of their packages.

deploy$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7
deploy$ sudo sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger $(lsb_release -cs) main > /etc/apt/sources.list.d/passenger.list'
deploy$ sudo apt-get update
deploy$ sudo apt-get install -y nginx-extras libnginx-mod-http-passenger
deploy$ if [ ! -f /etc/nginx/modules-enabled/50-mod-http-passenger.conf ]; then sudo ln -s /usr/share/nginx/modules-available/mod-http-passenger.load /etc/nginx/modules-enabled/50-mod-http-passenger.conf ; fi
deploy$ sudo ls /etc/nginx/conf.d/mod-http-passenger.conf

With NGINX and Passenger successfully installed, the next step involves directing Passenger to the correct Ruby version.

To initiate this process, open the Passenger config file using your preferred text editor, whether it's nano or vim.

deploy$ # If you want to use the Nano for editing
deploy$ sudo nano /etc/nginx/conf.d/mod-http-passenger.conf
deploy$ # If you want to use the Vim for editing
deploy$ sudo vim /etc/nginx/conf.d/mod-http-passenger.conf

We simply want to change the `passenger_ruby` line to match the following:

passenger_ruby /home/deploy/.rbenv/shims/ruby;

Save this file and we'll start NGINX.

$deploy$ sudo service nginx start

To verify whether NGINX is running, visit your server's public IP address in your browser. If NGINX is operational, you should see the "Welcome to NGINX" message.

Moving on, our next task is to replace this default NGINX server configuration with one tailored for our application.

$deploy$ sudo rm /etc/nginx/sites-enabled/default
$deploy$ # If you want to use the Nano for editing
$deploy$ sudo nano /etc/nginx/sites-enabled/myapp
$deploy$ # If you want to use the Vim for editing
$deploy$ sudo vim /etc/nginx/sites-enabled/myapp

We want the contents of our NGINX site to look like the following.

Change `myapp` to the name of your app. We'll use this same folder later on when we define our Capistrano deploy_to folder.

server {
  listen 80;
  listen [::]:80;

  server_name _;
  root /home/deploy/myapp/current/public;

  passenger_enabled on;
  passenger_app_env production;
  passenger_preload_bundler on;

  location /cable {
    passenger_app_group_name myapp_websocket;
    passenger_force_max_concurrent_requests_per_process 0;
  }

  # Allow uploads up to 100MB in size
  client_max_body_size 100m;

  location ~ ^/(assets|packs) {
    expires max;
    gzip_static on;
  }
}

Save the file and then we'll reload NGINX to load the new server files.

deploy$ sudo service nginx reload

Creating a PostgreSQL Database

To set up a PostgreSQL database, we'll begin by installing the PostgreSQL server and `libpq`, enabling the compilation of the pg rubygem.
Next, we'll switch to the `postgres` Linux user, which possesses full database access. We'll use this account to establish a new database user for our applications, naming it `deploy`.
Finally, the last command will create a database named `myapp`, with the deploy user designated as the owner. Be sure to replace `myapp` with the actual name of your application.

deploy$ sudo apt-get install postgresql postgresql-contrib libpq-dev
deploy$ sudo su - postgres
deploy$ createuser --pwprompt deploy
deploy$ createdb -O deploy myapp
deploy$ exit

You can manually connect to your database anytime by running psql -U deploy -W -h 127.0.0.1 -d myapp. Make sure to use 127.0.0.1 when connecting to the database instead of localhost.


Creating a MySQL Database

For MySQL, we'll install both the server and client libraries so we can compile the mysql2 rubygem.

deploy$ sudo apt-get install mysql-server mysql-client libmysqlclient-dev
deploy$ sudo mysql_secure_installation
deploy$ # Open the MySQL CLI to create the user and database
deploy$ mysql -u root -p

Now that we're within the MySQL command line interface, we can create a database for our app and a dedicated user with restricted access to this database. Establishing an app-specific user enhances security in the event of a compromise.

It might seem like we're creating two users because MySQL treats users over localhost differently than users over an IP address. This setup allows for both scenarios.

In the provided example, remember to replace the following names:

Replace `myapp` with the actual name of your database, typically reflecting your app's name.
Replace `$omeFancyPassword123` with your chosen password.
If you prefer a different name, replace `deploy` with your desired database user name.
mysql> CREATE DATABASE IF NOT EXISTS myapp;
mysql> CREATE USER IF NOT EXISTS 'deploy'@'localhost' IDENTIFIED BY '$omeFancyPassword123';
mysql> CREATE USER IF NOT EXISTS 'deploy'@'%' IDENTIFIED BY '$omeFancyPassword123';
mysql> GRANT ALL PRIVILEGES ON myapp.* TO 'deploy'@'localhost';
mysql> GRANT ALL PRIVILEGES ON myapp.* TO 'deploy'@'%';
mysql> FLUSH PRIVILEGES;
mysql> \q


Setting Up Capistrano
Back on our local machine, we can install Capistrano in our Rails app.
We'll need to add the following gems to our Gemfile:

gem 'capistrano', '~> 3.11'
gem 'capistrano-rails', '~> 1.4'
gem 'capistrano-passenger', '~> 0.2.0'
gem 'capistrano-rbenv', '~> 2.1', '>= 2.1.4'


Once added, we can run the following to install the gems and have Capistrano install its config files:

bundle
cap install STAGES=production

This generates several files for us:

`Capfile`
`config/deploy.rb`
`config/deploy/production.rb`

We need to edit the Capfile and add the following lines:

require 'capistrano/rails'
require 'capistrano/passenger'
require 'capistrano/rbenv'

set :rbenv_type, :user
set :rbenv_ruby, '3.3.0'

Then we can modify `config/deploy.rb` to define our application and git repo details.

set :application, "myapp"
set :repo_url, "git@github.com:username/myapp.git"

# Deploy to the user's home directory
set :deploy_to, "/home/deploy/#{fetch :application}"

append :linked_dirs, 'log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', '.bundle', 'public/system', 'public/uploads'

# Only keep the last 5 releases to save disk space
set :keep_releases, 5

# Optionally, you can symlink your database.yml and/or secrets.yml file from the shared directory during deploy
# This is useful if you don't want to use ENV variables
# append :linked_files, 'config/database.yml', 'config/secrets.yml'

Now we need to modify config/deploy/production.rb to point to our server's IP address for production deployments. Make sure to replace `1.2.3.4` with your server's public IP.

server '1.2.3.4', user: 'deploy', roles: %w{app db web}

Before we can deploy our app to production, we need to SSH into the server one last time and add our environment variables.
ssh deploy@1.2.3.4
deploy$ mkdir /home/deploy/myapp
deploy$ nano /home/deploy/myapp/.rbenv-vars

Add any environment variables you need for production to this file.

# For Postgres
DATABASE_URL=postgresql://deploy:PASSWORD@127.0.0.1/myapp

# For MySQL
DATABASE_URL=mysql2://deploy:$omeFancyPassword123@localhost/myapp

RAILS_MASTER_KEY=ohai
SECRET_KEY_BASE=1234567890

STRIPE_PUBLIC_KEY=x
STRIPE_PRIVATE_KEY=y
# etc...

Save this file and these environment variables will be automatically loaded every time you run Ruby commands inside your app's directory on the server.

Using this method, we can have separate env variables for every application we deploy to this server.

Now we can deploy our app to production:

cap production deploy

Open your server's IP in your browser and you should be greeted with your Rails application.