Skip to main content

Run Ruby Applications

This article explains how to run Ruby apps on Nine's managed servers.

Container Deployment

If you want to run your application in a container, please refer to Podman as a Managed Service.

A container keeps your application consistent across environments. Containers also make dependency management easier and help you scale your application by isolating it from other processes on the system.

Requirements

For the example below, we assume that your application supports a Ruby web server like puma, thin, etc. Rails comes pre-bundled with puma. Therefore we'll use that in this example.

1. Install the Ruby Version Manager

We recommend using a version manager like rbenv to be able to use different Ruby versions than provided by the distribution release of Ubuntu. It also allows you to use different versions per project.

To install rbenv:

curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/bin/rbenv-installer | bash
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc

2. Install Ruby Version

The desired Ruby version can now be installed:

rbenv install --list # list installable versions
rbenv install 3.2.0

Update your Bundler to the latest version:

rbenv exec bundle update --bundler

3. Deploy Application

Deploying your application is different for each project. With Rails, you need to compile assets, upload them and the code to the server, and install Ruby dependencies:

Run these commands on your local machine

Compile assets:

RAILS_ENV=production bundle exec rake assets:precompile

Copy files to server:

rsync -a -v --delete --exclude='node_modules/*' --exclude='tmp/*' --exclude='vendor/*' --exclude='.git/*' ./ www-data@server.nine.ch:app/current

Ensure dependencies are up-to-date:

ssh www-data@server.nine.ch 'cd ~/app/current && BUNDLER_WITHOUT="development test" rbenv exec bundle install'

4. Setup systemd Service

Use a systemd user service to keep a service running if it fails or on a server restart.

This example loads environment variables from ~/app/env and then executes rails server -b localhost --log-to-stdout in the directory ~/app/current.

The commands depend on your setup. The above example assumes that you're running a Rails application.

Create the service file in ~/.config/systemd/user/rails-app.service.

[Unit]
Description=Application

[Service]
Type=simple
WorkingDirectory=%h/app/current

Environment=RAILS_ENV=production
Environment=PORT=3000

EnvironmentFile=%h/app/env

ExecStart=%h/.rbenv/bin/rbenv exec bundle exec rails server -b localhost --log-to-stdout

TimeoutSec=15
Restart=on-failure

PrivateTmp=yes
ProtectSystem=full

[Install]
WantedBy=default.target

And then start the service:

touch ~/app/env # ensure environment file exists
systemctl --user daemon-reload
systemctl --user enable rails-app.service
systemctl --user start rails-app.service

Your application should now be registered as service and already running:

$ systemctl --user status rails-app.service
● rails-app.service - Application
Loaded: loaded (/home/www-data/.config/systemd/user/rails-app.service; enabled; vendor preset: enabled)
Active: active (running) since Fri 2021-07-16 10:13:36 CEST; 5s ago
Main PID: 3615317 (ruby)
CGroup: /user.slice/user-33.slice/user@33.service/rails-app.service
└─3615317 puma 5.3.2 (tcp://localhost:3000) [current]

Jul 16 10:13:37 server rbenv[3615317]: => Run `bin/rails server --help` for more startup options
Jul 16 10:13:37 server rbenv[3615317]: Puma starting in single mode...
Jul 16 10:13:37 server rbenv[3615317]: * Puma version: 5.3.2 (ruby 3.0.2-p107) ("Sweetnighter")
Jul 16 10:13:37 server rbenv[3615317]: * Min threads: 5
Jul 16 10:13:37 server rbenv[3615317]: * Max threads: 5
Jul 16 10:13:37 server rbenv[3615317]: * Environment: production
Jul 16 10:13:37 server rbenv[3615317]: * PID: 3615317
Jul 16 10:13:37 server rbenv[3615317]: * Listening on http://127.0.0.1:3000
Jul 16 10:13:37 server rbenv[3615317]: * Listening on http://[::1]:3000
Jul 16 10:13:38 server rbenv[3615317]: Use Ctrl-C to stop

Troubleshooting

  1. Ensure the .bashrc is sourced properly.
  2. Check if the service is running systemctl --user status app.service
  3. Check the logs using journalctl --user -f.

More information about how to use Systemd is available in the following support article.

5. Configure Webserver

https

This makes you application should be accessible via http. To add https support, please use the proxy_letsencrypt_https as documented in nine-manage-vhosts with Let's Encrypt.

The application is now running on a local port. To reach the application via http and https, the webserver needs to proxy these requests to the local port.

There are already two nine-manage-vhost templates for this use case pre-installed on your managed server: proxy and proxy_letsencrypt.

Create a new vHost and set the local port of the applications via template variable:

$ sudo nine-manage-vhosts virtual-host create example.com --template proxy --template-variable PROXYPORT=3000 --webroot ~/exampleapp/static

Virtual Host created: example.com
example.com
===========
DOMAIN: example.com
USER: www-data
WEBROOT: /home/www-data/exampleapp/static
TEMPLATE: proxy
TEMPLATE VARIABLES: PROXYPORT
3000
ALIASES: www.example.com
example.com.server.nine.ch

You can now test the connection by accessing http://example.com.server.nine.ch.

Automate Deployment

To automate the deployment of your Ruby application, you can use Capistrano, a remote server automation tool. Below are the steps to set up and use Capistrano for deployment:

Install Capistrano

Add Capistrano to your Gemfile:

group :deployment do
gem 'capistrano', require: false
gem 'capistrano-rbenv', require: false
gem 'capistrano-rbenv-install', require: false
gem 'capistrano-rails', require: false
gem 'capistrano-systemd-multiservice', require: false
end

Then run:

bundle install
cap install

This will create several default configuration files for Capistrano in your project.

Configure Capistrano

Edit the Capfile to include the necessary Capistrano plugins:

require 'capistrano/setup'
require 'capistrano/deploy'

require 'capistrano/scm/git'
install_plugin Capistrano::SCM::Git

require 'capistrano/rbenv'
require 'capistrano/rbenv_install'
require 'capistrano/bundler'
require 'capistrano/rails'

require "capistrano/systemd/multiservice"
install_plugin Capistrano::Systemd::MultiService.new_service("app", service_type: "user")

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }

Edit config/deploy.rb to define the deployment settings:

lock "~> 3.16"

set :application, "your_app_name"
set :repo_url, "git@example.com:me/my_repo.git"

set :branch, ENV.fetch("CI_COMMIT_REF_NAME", "main")
set :deploy_to, "/home/#{fetch :user}/#{fetch :application}"
set :keep_releases, 5

set :rbenv_type, :user
set :rbenv_ruby, File.read(".ruby-version").strip
set :rbenv_path, "/home/#{fetch :user}/.rbenv"

append :linked_files, "env"
append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "vendor/bundle", "public/system"

before "systemd:app:validate", "systemd:app:setup"
after "deploy:publishing", "systemd:app:restart"

Set up the server details in config/deploy/production.rb:

server 'server.nine.ch', user: 'www-data', roles: %w{app db web}

Define the Ruby-Version in a .ruby-version file:

3.2.0

And finally, create a systemd service template in config/systemd/app.service.erb:

[Unit]
Description=<%= fetch :application %> Rails Application

[Service]
Type=simple

WorkingDirectory=<%= current_path %>
Environment=RAILS_SERVE_STATIC_FILES=true
EnvironmentFile=<%= shared_path %>/env

ExecStart=%h/.rbenv/bin/rbenv exec bundle exec rails server -b localhost --log-to-stdout

TimeoutSec=15
Restart=on-failure

PrivateTmp=yes
ProtectSystem=full

[Install]
WantedBy=default.target

Deploy with Capistrano

Now you can deploy your application using Capistrano with the following command:

bundle exec cap production deploy

Capistrano handles the details of deployment, including Ruby version management, dependencies, asset compilation, file copying, and managing the systemd service.

This approach ensures that your deployment process is streamlined and less prone to errors, thereby enabling more efficient and reliable releases.