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