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
sudoaccess. - 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.