Ruby on Rails (Rails) is a full-stack web application framework built on Ruby that made convention-over-configuration and the MVC pattern mainstream in web development. Rails provides ActiveRecord (ORM), ActionController (routing and controllers), ActionView (templating with ERB/Haml), ActionMailer, ActiveJob (background jobs), ActionCable (WebSockets), and a comprehensive set of security defaults out of the box. A Rails application deployed in production uses Puma as the multi-threaded application server. Nginx handles SSL termination, static asset serving, and reverse proxying to Puma. This guide covers deploying a Rails 7 application on RHEL 9 with Puma, PostgreSQL, and Nginx, including asset precompilation and database migrations.
Prerequisites
- Ruby 3.3 with rbenv installed on RHEL 9
- PostgreSQL 16, Nginx, and Node.js installed (Node.js required for asset compilation)
Step 1 — Create a New Rails Application
gem install rails
rbenv rehash
cd /var/www
rails new myapp --database=postgresql --asset-pipeline=propshaft
cd myapp
Step 2 — Configure the Database
# config/database.yml
production:
adapter: postgresql
encoding: unicode
pool:
database: myapp_production
username: myapp_user
password:
host: 127.0.0.1
port: 5432
psql -U postgres <<'SQL'
CREATE DATABASE myapp_production;
CREATE USER myapp_user WITH PASSWORD 'RailsDBPass123!';
GRANT ALL PRIVILEGES ON DATABASE myapp_production TO myapp_user;
SQL
RAILS_ENV=production DB_PASSWORD=RailsDBPass123! rails db:migrate
Step 3 — Configure Puma for Production
# config/puma.rb — production Puma configuration
workers Integer(ENV.fetch("WEB_CONCURRENCY") { 2 })
threads_count = Integer(ENV.fetch("RAILS_MAX_THREADS") { 5 })
threads threads_count, threads_count
bind "unix:///run/myapp.sock"
environment ENV.fetch("RAILS_ENV") { "development" }
preload_app!
on_worker_boot do
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
Step 4 — Precompile Assets and Create systemd Service
RAILS_ENV=production SECRET_KEY_BASE=$(rails secret) rails assets:precompile
# /etc/systemd/system/myapp.service
[Unit]
Description=Rails App (Puma)
After=network.target postgresql.service
[Service]
Type=simple
User=rails
Group=rails
WorkingDirectory=/var/www/myapp
Environment=RAILS_ENV=production
Environment=DB_PASSWORD=RailsDBPass123!
EnvironmentFile=/var/www/myapp/.env
ExecStart=/home/rails/.rbenv/shims/bundle exec puma -C config/puma.rb
Restart=always
[Install]
WantedBy=multi-user.target
systemctl daemon-reload && systemctl enable --now myapp
Step 5 — Nginx Configuration
# /etc/nginx/conf.d/myapp.conf
upstream rails_app {
server unix:///run/myapp.sock fail_timeout=0;
}
server {
listen 80;
server_name myapp.example.com;
root /var/www/myapp/public;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
location / {
try_files $uri/index.html $uri @rails_app;
}
location @rails_app {
proxy_pass http://rails_app;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
}
}
Conclusion
Ruby on Rails 7 with Puma and Nginx on RHEL 9 provides a complete production-ready Ruby web stack. preload_app! in the Puma configuration uses the copy-on-write fork behaviour to pre-load the Rails application before forking workers, reducing memory usage and startup time. For zero-downtime deployments, use Puma’s phased restart feature (bundle exec pumactl phased-restart) which gradually replaces workers without dropping connections.
Next steps: How to Install Ruby with rbenv on RHEL 9, How to Configure PostgreSQL on RHEL 9, and How to Install Redis on RHEL 9.