How to Deploy a Ruby on Rails Application on RHEL 7

Ruby on Rails is a full-featured web application framework that follows the Model-View-Controller (MVC) pattern and emphasizes convention over configuration. Deploying a Rails application on RHEL 7 in a production-grade configuration requires several components working together: Rails itself running under Puma (a multi-threaded Ruby application server), a systemd service to manage the process lifecycle, and Nginx acting as a reverse proxy to handle incoming HTTP requests and forward them to Puma over a Unix domain socket. This tutorial covers the complete deployment pipeline from gem installation through a production-ready Nginx configuration, using either MySQL or PostgreSQL as the database backend.

Prerequisites

  • RHEL 7 with sudo access.
  • Ruby 3.1.x installed via rbenv (see the rbenv tutorial).
  • Bundler installed (gem install bundler).
  • A MySQL or PostgreSQL server installed and running on the same host or accessible remotely.
  • Nginx installed: sudo yum install -y nginx.
  • Node.js installed for the Rails asset pipeline: sudo yum install -y nodejs.

Step 1: Install Rails

Install the Rails gem using the gem command. Specifying --no-document skips ri and rdoc generation, which significantly speeds up gem installation on servers where documentation is not needed.

gem install rails --no-document
rbenv rehash
rails --version
Rails 7.1.3

Step 2: Install Database Client Libraries

The Rails database adapter gems (mysql2 or pg) require the database client development headers to compile their native extensions. Install the appropriate package for your database:

For MySQL / MariaDB:

sudo yum install -y mariadb-server mariadb-devel
sudo systemctl enable mariadb
sudo systemctl start mariadb
sudo mysql_secure_installation

For PostgreSQL:

sudo yum install -y postgresql-server postgresql-devel
sudo postgresql-setup initdb
sudo systemctl enable postgresql
sudo systemctl start postgresql

Step 3: Create a New Rails Application

Generate a new Rails application. Use -d mysql or -d postgresql depending on your chosen database:

cd /var/www
sudo mkdir myapp
sudo chown $(whoami):$(whoami) myapp
rails new myapp -d mysql
cd myapp

For PostgreSQL, replace -d mysql with -d postgresql.

Step 4: Configure database.yml

Edit config/database.yml to match your database credentials. Rails generates a template; fill in the connection details:

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: rails_user
  password: <%= ENV["DB_PASSWORD"] %>
  host: 127.0.0.1

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  <<: *default
  database: myapp_production

Store the database password in an environment variable rather than hardcoding it. Set DB_PASSWORD in the app’s environment file or systemd unit (covered later).

Create the database user and grant permissions:

sudo mysql -u root -p
CREATE USER 'rails_user'@'localhost' IDENTIFIED BY 'strongpassword';
CREATE DATABASE myapp_production CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON myapp_production.* TO 'rails_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Step 5: Install Gems and Configure Puma

Add Puma to your Gemfile if it is not already present (Rails 7 includes it by default). Open Gemfile and ensure:

gem "puma", "~> 6.0"

Then install all gems:

bundle install

Configure Puma for production by editing config/puma.rb. A typical production configuration uses a Unix socket and multiple worker threads:

max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"

port ENV.fetch("PORT") { 3000 }

environment ENV.fetch("RAILS_ENV") { "development" }

pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

workers ENV.fetch("WEB_CONCURRENCY") { 2 }

preload_app!

plugin :tmp_restart

# Bind to Unix socket for Nginx reverse proxy
bind "unix:///var/www/myapp/tmp/sockets/puma.sock"

Create the required directories:

mkdir -p tmp/pids tmp/sockets log

Step 6: Prepare the Database and Precompile Assets

RAILS_ENV=production bundle exec rails db:create
RAILS_ENV=production bundle exec rails db:migrate
RAILS_ENV=production bundle exec rails assets:precompile

Generate a secret key base and set it as an environment variable:

bundle exec rails secret

Copy the output string — you will need it in the systemd unit file.

Step 7: Create a systemd Service for Puma

Create a systemd unit file to manage the Puma process as a system service, ensuring it starts on boot and restarts automatically on failure.

sudo tee /etc/systemd/system/myapp.service <<'EOF'
[Unit]
Description=Puma HTTP Server for MyApp
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
EnvironmentFile=/etc/myapp/environment
ExecStart=/home/deploy/.rbenv/shims/bundle exec puma -C /var/www/myapp/config/puma.rb
ExecReload=/bin/kill -TSTP $MAINPID
StandardOutput=append:/var/www/myapp/log/puma.log
StandardError=append:/var/www/myapp/log/puma_error.log
Restart=on-failure
RestartSec=5
KillMode=mixed
TimeoutStopSec=5

[Install]
WantedBy=multi-user.target
EOF

Create the environment file referenced above:

sudo mkdir -p /etc/myapp
sudo tee /etc/myapp/environment <<'EOF'
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=true
RAILS_MAX_THREADS=5
WEB_CONCURRENCY=2
SECRET_KEY_BASE=your_generated_secret_key_base_here
DB_PASSWORD=strongpassword
EOF
sudo chmod 600 /etc/myapp/environment

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
sudo systemctl status myapp

Step 8: Configure Nginx as a Reverse Proxy

Nginx sits in front of Puma, handling static file serving, SSL termination, and forwarding dynamic requests to Puma via the Unix socket.

sudo tee /etc/nginx/conf.d/myapp.conf <<'EOF'
upstream puma {
    server unix:///var/www/myapp/tmp/sockets/puma.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com www.example.com;

    root /var/www/myapp/public;
    access_log /var/log/nginx/myapp_access.log;
    error_log  /var/log/nginx/myapp_error.log;

    location ^~ /assets/ {
        gzip_static on;
        expires max;
        add_header Cache-Control public;
    }

    try_files $uri/index.html $uri @puma;

    location @puma {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://puma;
    }

    error_page 500 502 503 504 /500.html;
    client_max_body_size 10M;
    keepalive_timeout 10;
}
EOF

Test the Nginx configuration and reload:

sudo nginx -t
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl reload nginx

Step 9: Open the Firewall

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

If SELinux is enforcing, allow Nginx to proxy to the Unix socket:

sudo setsebool -P httpd_can_network_connect 1

Your Rails application is now running in production on RHEL 7, served by Puma behind Nginx with a systemd-managed process. This stack is stable, widely deployed, and suitable for production workloads. From here you can add SSL with Let’s Encrypt, set up log rotation with logrotate, and implement a deployment workflow using Capistrano or a CI/CD pipeline to automate zero-downtime deployments by sending a TSTP signal to Puma, which gracefully finishes in-flight requests before reloading.