Why self-host Rails on a VPS?
A production Rails application combines several processes: the Puma web server, Sidekiq workers for asynchronous jobs, PostgreSQL and Redis. Managed platforms bill each dyno or worker separately, which drives up the bill as soon as the application grows. On a VPS, all these processes coexist on a single server that you control: you set the number of Puma threads and workers, you launch as many Sidekiq workers as RAM allows, and you manage asset precompilation and caching. With Rails 7 and later, the native integration of Propshaft, Solid Queue and Solid Cache simplifies self-hosting even further. You keep a fixed cost and a fully transparent stack.
Concrete benefits of a self-hosted Rails
- Puma and Sidekiq on the same VPS, without billing each worker separately.
- Tuning the number of Puma threads and workers according to cores and RAM.
- Unlimited background jobs with Sidekiq/Redis or Solid Queue.
- Controlled PostgreSQL: indexes,
pg_dumpbackups and connection tuning. - Asset precompilation and Rails caching handled locally, at no extra cost.
- Fixed and predictable cost, ideal for growing SaaS and business applications.
Hardware and software prerequisites
Rails is more RAM-hungry than average because of the Puma and Sidekiq workers: aim for 2 GB minimum, and 4 GB as soon as you launch several workers or add Redis and Elasticsearch. Install Ruby 3.2+ via rbenv or asdf, or use a ruby:3.3-slim Docker image. Plan for PostgreSQL 15, Redis for Sidekiq and caching, Node.js if you use a JS bundler, and Bundler. Nginx serves as a front end for TLS and static assets. A domain pointed at the VPS is required. Set RAILS_ENV=production and generate a SECRET_KEY_BASE.
Deploy Rails step by step
Prepare the server and the application
Over SSH, install Ruby 3.3, PostgreSQL and Redis (or Docker). Clone the repository and configure the variables: DATABASE_URL, REDIS_URL, RAILS_MASTER_KEY and SECRET_KEY_BASE. Run bundle install --without development test.
Precompile the assets and migrate
Run RAILS_ENV=production bin/rails assets:precompile then bin/rails db:migrate. With Rails 7+, Propshaft and importmap simplify the asset pipeline without a complex Node build.
Launch Puma
Start the server via bundle exec puma -C config/puma.rb, setting WEB_CONCURRENCY (workers) and RAILS_MAX_THREADS in the environment. Have Puma listen on a Unix socket for better performance with Nginx, and manage the process via systemd.
Start the Sidekiq workers
Create a systemd service that runs bundle exec sidekiq -e production -C config/sidekiq.yml, with automatic restart. Adjust the number of Sidekiq threads according to the job load and the available memory.
Configure Nginx and SSL
Point Nginx to the Puma socket via upstream and proxy_pass, serve public/assets directly with a long cache, then obtain a Let's Encrypt certificate with Certbot for yourdomain.com. Force HTTPS and automatic renewal.
Automate deployments
Set up a script or Kamal/Capistrano that chains git pull, bundle install, db:migrate, assets:precompile and the restart of Puma and Sidekiq. Atomic deployment with symbolic links lets you roll back instantly if a problem occurs.
Watch Puma's memory footprint: Ruby processes bloat over time due to memory fragmentation. Enable jemalloc as the allocator (via LD_PRELOAD) to significantly reduce the RAM consumed by workers, and configure a periodic restart of the Puma workers. On a VPS where RAM is the most limiting resource for Rails, this single tweak can save you from having to upgrade.