Deploy Ruby on Rails app on Ubuntu 24.04 using CloudRay

Streamline your Ruby on Rails deployments with the power of Bash scripting and CloudRay. This guide provides a step-by-step approach to automating your deployment process, allowing you to push updates to your application quickly and efficiently.

Contents

Prerequisites

Before we begin, ensure you have the following:

  • A CloudRay account at https://app.cloudray.io/
  • A Ruby on Rails application that is ready to deploy.
  • A cloud server accessible via SSH: If you don’t already have a cloud server, you can get one from popular providers like AWS, DigitalOcean, and Google Cloud.
  • SSH credentials: Ensure you have access to the necessary SSH keys or login credentials to access your server.
  • GitHub Access Token: You’ll need a fine-grained personal access token to clone your repository. Follow our GitHub Access Token guide to create one.

This guide focuses on deploying Rails applications with Puma. For deploying background workers (e.g., Delayed Jobs), refer to the following guide:

NOTE

This guide uses Bash scripts, providing a high degree of customisation. You can adapt the scripts to fit your specific deployment needs and environment.

Assumptions

  • This guide assumes that the database is hosted on a separate server. Installing and configuring the database on the same server as the Rails application is not within the scope of this document. You can refer to other articles for instructions on setting up a database server.
  • This guide assumes you’re using Ubuntu 24.04 LTS as your server’s operating system. If you’re using a different version or a different distribution, adjust the commands accordingly.

Add your server to CloudRay

If you’ve already added your server to CloudRay, you can skip this step.

To deploy your application, you need to add your server to CloudRay so it can execute deployment scripts.

Steps to add your server:

  1. Log in to your CloudRay account.
  2. Navigate to Servers in the dashboard.
  3. Click New Server.
  4. Follow the on-screen instructions.

For detailed guidance, refer to the Adding a Server documentation.

Once your server is added, proceed to the next step.

Create the setup script

To streamline the deployment process, you’ll need two Bash scripts:

  1. Setup Script: You’ll run this once when setting up a new server to install dependencies and configure services.
  2. Deployment Script: You can run this anytime you want to update your application with new code.

In this section, we’ll create the Setup Script.

The setup script prepares your server by:

  • Creating a deploy user that runs Rails server as a non-privileged service
  • Installing Caddy web server for automatic HTTPS
  • Setting up asdf to install Ruby and Node.js
  • Creating a systemd service to manage the Rails server
  • Setting up the repository for the first time

To create the setup script:

  1. Go to Scripts in your CloudRay project
  2. Click New Script
  3. Name: setup-rails-server
  4. Copy this code:
# Stop executing the script if any of the commands fail
set -e

# Create a deploy user if it doesn't exist
# This is the user that will run the Rails application
if [ ! -d /home/deploy ]; then
  echo "Creating deploy user"
  adduser --disabled-password --gecos "" deploy
fi

# We'll deploy the Rails app to /srv
# let's ensure the deploy user has the right permissions
echo "Setting up /srv directory"
chown deploy:deploy -R /srv

# Update the server
echo "Updating the server"
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get dist-upgrade -y

echo "Installing essential packages"
apt-get install -y git caddy

# Additional packages for Nokogiri
echo "Installing additional packages for Ruby & Nokogiri"
apt-get install -y build-essential patch zlib1g-dev liblzma-dev libyaml-dev libreadline-dev libffi-dev

# Remove these lines if you don't need MySQL libraries for the mysql gem
echo "Installing packages for mysql2"
apt-get install -y libmysqlclient-dev

# Remove these lines if you don't need PostgreSQL libraries for the pg gem
echo "Installing packages for pg"
apt-get install -y libpq-dev

echo "Configuring Caddy to serve the Rails application"
cat > /etc/caddy/Caddyfile <<'EOT'
{{app_domain}} {
  root * /srv/{{app_name}}/public
  encode zstd gzip
  file_server
  @notStatic not file
  reverse_proxy @notStatic unix//srv/{{app_name}}/tmp/puma.sock
}
EOT

su -l deploy <<'EOT'
  set -e
  if [ ! -d "$HOME/.asdf" ]; then
    echo "Installing asdf"
    git clone https://github.com/asdf-vm/asdf.git ~/.asdf
    echo "source $HOME/.asdf/asdf.sh" >> ~/.bashrc
  else
    echo "Updating asdf"
    cd ~/.asdf
    git pull
  fi

  source $HOME/.asdf/asdf.sh

  if ! grep -q "legacy_version_file" ~/.asdfrc; then
    echo "legacy_version_file = yes" >> ~/.asdfrc
  fi
  asdf plugin add ruby
  asdf plugin add nodejs
  echo "Installing Ruby version {{ruby_version}}"
  asdf install ruby {{ruby_version}}
  echo "Installing Node.js version {{node_version}}"
  asdf install nodejs {{node_version}}
  asdf reshim
  echo "Finished installing Ruby and Node.js"
EOT

echo "Creating the {{app_name}}-rails-server service"
cat <<'EOT' > /etc/systemd/system/{{app_name}}-rails-server.service
[Unit]
Description={{app_name}}-rails
After=network.target

[Service]
Type=simple
User=deploy
Environment=RAILS_ENV=production
EnvironmentFile=/etc/environment
WorkingDirectory=/srv/{{app_name}}
ExecStart=/bin/bash -lc "source $HOME/.asdf/asdf.sh && rails server -b /srv/{{app_name}}/tmp/puma.sock"
Restart=always

[Install]
WantedBy=multi-user.target
EOT

echo "Enabling the {{app_name}}-rails-server service"
systemctl enable {{app_name}}-rails-server.service

echo "Setting up the repository for the first time"
su -l deploy <<'EOT'
  set -e
  source "$HOME/.asdf/asdf.sh"
  mkdir -p /srv/{{app_name}}
  cd /srv/{{app_name}}
  if [ ! -d "/srv/{{app_name}}/.git" ]; then
    git clone https://{{github_access_token}}@github.com/{{github_repo_name}} .
  fi
EOT

echo "Done."

TIP

If you find this script too long to manage, you can break it into smaller scripts and use Script Playlists to run them together.

Create the deployment script

You’ll use this script whenever you want to update your application. The script:

  • Pulls latest code from your Git repository’s main branch
  • Updates Ruby dependencies and installs new gems
  • Precompiles JavaScript/CSS assets for production
  • Runs any pending database migrations safely
  • Restarts the Rails server

To create the deployment script:

  1. Go to Scripts > New Script
  2. Name: deploy-rails-app
  3. Add code:
set -e

su -l deploy <<'EOT'
set -e
. "$HOME/.asdf/asdf.sh"
cd /srv/{{app_name}}
git fetch --all
git reset --hard origin/main
bundle
echo "{{rails_master_key}}" > config/master.key
RAILS_ENV=production bundle exec rails assets:precompile
RAILS_ENV=production bundle exec rails db:migrate
EOT

echo "Restarting processes"
systemctl restart {{app_name}}-rails-server.service

echo "🚀 Deployed {{app_name}} at {{app_domain}}"

Create a variable group

Our scripts use variables like {{app_name}}, {{app_domain}}, {{ruby_version}}, and {{node_version}} because CloudRay processes all scripts as Liquid templates. This allows you to use placeholders in your scripts, making them dynamic and reusable across different servers.

To provide values for these variables, you’ll need to create a variable group. Here’s how:

  1. Navigate to Variable Groups: In your CloudRay project, go to “Scripts” in the top menu and click on “Variable Groups”.
  2. Create a new Variable Group: Click on “New Variable Group”.
  3. Add the following variables:
  • app_name: Your application’s name (use underscores instead of spaces), e.g., my_app
  • app_domain: Your application’s domain name, e.g., myapp.example.com
  • ruby_version: The version of Ruby you want to install. E.g., 3.2.2
  • node_version: The version of Node.js you want to install. E.g., 20.0.0
  • rails_master_key: Your Rails master key from config/master.key.
  • github_access_token: Your GitHub personal access token for cloning the repository.
  • github_repo_name: Your GitHub repository in the format username/repository, e.g., yourusername/your-repo.

Run the setup script

To run the setup-rails-server script, you’ll use a Runlog in CloudRay. A Runlog allows you to execute scripts on your servers and provides detailed logs of the execution process.

Here’s how to create and run a Runlog:

  1. Navigate to Runlogs: In your CloudRay project, go to “Runlogs” in the top menu.
  2. Create a new Runlog: Click on “New Runlog”.
  3. Fill in the form:
  • Server: Select the server you added earlier.
  • Script: Choose the setup-rails-server script.
  • Variable Group: Select the variable group you created earlier.
  1. Run the script: Click on “Run” to execute the script on your server.

CloudRay will connect to your server, run the setup-rails-server script, and show you the live output as the script executes.

Run the deployment script

You can run the deploy-rails-app script whenever you want to deploy a new version of your application. Here’s how to create a Runlog for the deployment script:

  1. Navigate to Runlogs: In your CloudRay project, go to “Runlogs” in the top menu.
  2. Create a new Runlog: Click on “New Runlog”.
  3. Fill in the form:
  • Server: Select the server you added earlier.
  • Script: Choose the deploy-rails-app script.
  • Variable Group: Select the variable group you created earlier.
  1. Run the script: Click on “Run” to execute the script on your server.

CloudRay will connect to your server, run the deploy-rails-app script, and show you the live output as the script executes.

Troubleshooting

Running out of memory when installing Ruby

If you encounter an “out of memory” error during the Ruby installation using asdf install ruby, it’s likely because the /tmp directory is mounted in RAM with limited space. The build process requires more space than is available in the RAM-mounted /tmp.

To resolve this issue, you can disable the RAM mount for /tmp so it uses disk storage instead.

Create a new script with following code and run it on your server:

set -e
echo "Disable mounting /tmp in RAM otherwise ruby-build will run out of memory"
systemctl mask tmp.mount
reboot

That’s it! Happy deploying!