Ruby on Rails is a full-stack web framework that prioritises convention over configuration, making it possible to stand up a production-ready application in minutes. Deploying Rails on RHEL 8 involves generating the application, running database migrations, serving it through the built-in Puma application server, and placing Nginx in front as a reverse proxy. This tutorial walks through each stage and shows how to keep Puma running reliably as a systemd service.

Prerequisites

  • Ruby 3.2.0 installed via rbenv with Bundler available (see How to Install Ruby with rbenv on RHEL 8)
  • Node.js and Yarn installed for asset compilation (dnf module install -y nodejs:18 && npm install -g yarn)
  • PostgreSQL or SQLite3 development headers (dnf install -y sqlite-devel for SQLite)
  • Nginx installed (dnf install -y nginx) and firewall ports 80/443 open

Step 1 — Create a New Rails Application

Install the Rails gem globally for the current rbenv Ruby version, then generate a new application.

gem install rails
rbenv rehash
rails new myapp --database=sqlite3
cd myapp

Install all gem dependencies declared in the generated Gemfile:

bundle install

Step 2 — Set Up the Database

Create and migrate the database. In development this creates a local SQLite file; in production you would set the DATABASE_URL environment variable instead.

rails db:create db:migrate

Confirm the application boots in development mode before moving to production configuration:

rails server -b 0.0.0.0 -p 3000

Visit http://<your-server-ip>:3000 in a browser; you should see the Rails welcome page. Stop the server with Ctrl+C.

Step 3 — Configure the Production Environment

Rails requires two environment variables in production: a secret key base for session encryption, and the Rails environment flag.

export RAILS_ENV=production
export SECRET_KEY_BASE=$(rails secret)

Add these permanently to /etc/environment or to the systemd service unit (covered in Step 5). Compile static assets for production:

RAILS_ENV=production bundle exec rails assets:precompile

Run production database migrations:

RAILS_ENV=production bundle exec rails db:migrate

Step 4 — Configure Nginx as a Reverse Proxy

Puma listens on 127.0.0.1:3000 by default. Create an Nginx server block to proxy public traffic to Puma.

sudo tee /etc/nginx/conf.d/myapp.conf > /dev/null << 'EOF'
server {
    listen 80;
    server_name yourdomain.com;

    root /home/deploy/myapp/public;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location ~* .(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}
EOF

Test and reload Nginx:

sudo nginx -t
sudo systemctl enable --now nginx

Step 5 — Create a systemd Service for Puma

A systemd unit ensures Puma starts on boot, restarts on failure, and runs under a dedicated user account.

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

[Service]
Type=simple
User=deploy
WorkingDirectory=/home/deploy/myapp
Environment=RAILS_ENV=production
Environment=SECRET_KEY_BASE=REPLACE_WITH_YOUR_SECRET
ExecStart=/home/deploy/.rbenv/shims/bundle exec puma -C config/puma.rb
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

Reload systemd, then enable and start the service:

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

Step 6 — Verify the Deployment

Check that Puma is listening on port 3000 and that Nginx is proxying correctly.

curl -I http://127.0.0.1:3000
curl -I http://yourdomain.com

Both requests should return an HTTP 200 response. Inspect Puma logs if there are issues:

sudo journalctl -u myapp -f

Conclusion

You have deployed a Ruby on Rails application on RHEL 8 using Puma as the application server and Nginx as a reverse proxy, with production assets precompiled and a systemd unit keeping the process alive. This setup forms a solid foundation for serving Rails applications reliably in production. For HTTPS, add a TLS certificate via Certbot. For zero-downtime deploys, consider Capistrano or a CI/CD pipeline that issues a systemctl restart myapp at the end of each deployment.

Next steps: How to Install Ruby with rbenv on RHEL 8, How to Install .NET SDK and Runtime on RHEL 8, and How to Install Deno on RHEL 8.