You built your Rails app. It works beautifully on your laptop.

Now you need to get it online.

The options are overwhelming. Heroku? Fly.io? Render? AWS? Kamal? Each one promises something different. Each one has its own quirks.

Let me cut through the noise. Here's how to deploy Rails to production, with real examples and honest trade-offs.


The Short Answer

Platform Best For Monthly Cost (Minimum) Complexity
Fly.io Most apps $5-15 Low
Render Simplicity $7-19 Very Low
Heroku Legacy apps, add-ons $25+ Low
Kamal + VPS Control, multiple apps $10-20 Medium
AWS ECS Enterprise scale $30+ High

For most developers, start with Fly.io or Render. They're simple, affordable, and handle everything you need.


Part 1: What You Need Before Deploying

Before you deploy anything, make sure your app is ready.

The Production Checklist

- [ ] Database uses PostgreSQL (not SQLite)
- [ ] Secrets stored in environment variables
- [ ] Assets precompile correctly
- [ ] Error tracking configured (Sentry, Honeybadger)
- [ ] Logging outputs to STDOUT
- [ ] Health check endpoint exists
- [ ] SSL enabled (most platforms do this automatically)
- [ ] Database migrations run automatically on deploy

Environment Variables You'll Need

# Required for most Rails apps
SECRET_KEY_BASE=your_long_random_string
DATABASE_URL=postgresql://user:pass@host/db
RAILS_ENV=production
RAILS_LOG_TO_STDOUT=true

# Payment processing
STRIPE_PUBLIC_KEY=pk_live_xxx
STRIPE_SECRET_KEY=sk_live_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx

# Email (SendGrid, Mailgun, etc.)
SMTP_ADDRESS=smtp.sendgrid.net
SMTP_USERNAME=apikey
SMTP_PASSWORD=your_sendgrid_api_key
SMTP_DOMAIN=yourdomain.com

# Error tracking
SENTRY_DSN=https://xxx@sentry.io/xxx

The Health Check Endpoint

# config/routes.rb
get "/up", to: "health#show"
# app/controllers/health_controller.rb
class HealthController < ApplicationController
  def show
    # Check database connection
    ActiveRecord::Base.connection.execute("SELECT 1")

    # Check Redis if used
    Redis.current.ping if defined?(Redis)

    render plain: "OK", status: :ok
  rescue => e
    render plain: "ERROR: #{e.message}", status: :internal_server_error
  end
end

Part 2: Option 1 - Fly.io (Recommended for Most)

Fly.io runs your app on servers close to your users. They handle SSL, scaling, and global distribution.

Why Choose Fly.io

Pros Cons
Simple deployment PostgreSQL requires separate app
Global load balancing Learning curve for advanced features
Free for small apps (3 shared VMs) CLI-only (no web dashboard for deploy)
Built-in PostgreSQL option
Great documentation

Step-by-Step Deployment

1. Install Fly CLI

# Mac
brew install flyctl

# Linux
curl -L https://fly.io/install.sh | sh

# Windows (WSL)
curl -L https://fly.io/install.sh | sh

2. Sign in

fly auth signup
# or
fly auth login

3. Launch your app

fly launch

This creates a fly.toml file with your app configuration.

4. Set up PostgreSQL (optional but recommended)

fly postgres create

This creates a separate PostgreSQL app. Connect your Rails app to it.

5. Set environment variables

fly secrets set SECRET_KEY_BASE=$(rails secret)
fly secrets set RAILS_MASTER_KEY=$(cat config/master.key)
fly secrets set SENTRY_DSN=your_dsn

6. Deploy

fly deploy

7. Open your app

fly open

The fly.toml File

# fly.toml
app = "myapp"
primary_region = "iad"

[build]
  [build.args]
    NODE_VERSION = "18"
    RUBY_VERSION = "3.2.0"

[env]
  RAILS_ENV = "production"
  RAILS_LOG_TO_STDOUT = "true"
  RAILS_SERVE_STATIC_FILES = "true"

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = false
  auto_start_machines = true
  min_machines_running = 1

[[services]]
  internal_port = 3000
  protocol = "tcp"

  [[services.ports]]
    port = 80
    handlers = ["http"]

  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

Common Fly.io Commands

# View logs
fly logs

# SSH into the machine
fly ssh console

# Run rails console
fly ssh console -C "rails console"

# Run migrations
fly ssh console -C "rails db:migrate"

# Scale to multiple machines
fly scale count 2

# Deploy with specific version
fly deploy --image "registry.fly.io/myapp:latest"

# List secrets
fly secrets list

Part 3: Option 2 - Render

Render is the easiest deployment option. No configuration files. No CLI learning curve.

Why Choose Render

Pros Cons
Web-based deployment More expensive than Fly.io
Automatic HTTPS Less global distribution
Built-in PostgreSQL with auto-backups Fewer advanced features
No YAML configuration
Great developer experience

Step-by-Step Deployment

1. Push your code to GitHub

git push origin main

2. Create a new Web Service on Render

  • Go to dashboard.render.com
  • Click New +Web Service
  • Connect your GitHub repository
  • Select Ruby as the environment

3. Configure the service

Setting Value
Name myapp
Root Directory (leave blank)
Environment Ruby
Build Command bundle install && bundle exec rake assets:precompile
Start Command bundle exec puma -C config/puma.rb

4. Add PostgreSQL

  • Click New +PostgreSQL
  • Name it myapp-db
  • Choose your plan (Free tier has 1GB storage)

5. Set environment variables

In your Web Service settings:

RAILS_ENV=production
SECRET_KEY_BASE=<generate with `rails secret`>
RAILS_MASTER_KEY=<your master key>
DATABASE_URL=<copied from PostgreSQL dashboard>

6. Deploy

Render automatically deploys when you push to GitHub.

Build and Start Commands

If you need custom commands:

# Build command
bundle install
bundle exec rake assets:precompile
bundle exec rake db:migrate

# Start command
bundle exec puma -C config/puma.rb

Part 4: Option 3 - Kamal (For Control)

Kamal deploys to your own VPS (DigitalOcean, Hetzner, AWS). You keep full control.

Why Choose Kamal

Pros Cons
Full control over infrastructure More complex setup
Lower costs at scale You manage servers
Deploy to any VPS No built-in monitoring
Zero downtime deploys
Uses your existing Dockerfile

Step-by-Step Deployment

1. Install Kamal

gem install kamal

2. Set up a VPS

Create a VPS on DigitalOcean, Hetzner, or any provider. Ubuntu 22.04 recommended.

3. Initialize Kamal

kamal init

This creates config/deploy.yml.

4. Configure deploy.yml

# config/deploy.yml
service: myapp
image: myapp
servers:
  web:
    - 123.123.123.123
  workers:
    - 123.123.123.123

registry:
  server: ghcr.io
  username: your_github_username
  password:
    - KAMAL_REGISTRY_PASSWORD

env:
  clear:
    RAILS_ENV: production
    RAILS_LOG_TO_STDOUT: true
  secret:
    - DATABASE_URL
    - SECRET_KEY_BASE
    - SENTRY_DSN

volumes:
  - "storage:/rails/storage"

asset_path: /rails/public/assets

builder:
  arch: amd64

ssh:
  user: root

5. Prepare the server

kamal server bootstrap

6. Deploy

kamal deploy

Kamal Commands

# Deploy
kamal deploy

# Rollback
kamal rollback

# Run rails console
kamal app exec -i "rails console"

# Run migrations
kamal app exec -i "rails db:migrate"

# View logs
kamal app logs

# Restart app
kamal app restart

# Destroy everything
kamal teardown

Part 5: Option 4 - Heroku (Legacy)

Heroku was the standard for years. It still works well, but it's expensive.

Why Choose Heroku

Pros Cons
Battle-tested Expensive for production
Excellent add-ons Sleeps after 30 minutes (free tier)
Simple git push deploy Limited resources at low tiers
Great documentation

Step-by-Step Deployment

1. Install Heroku CLI

# Mac
brew install heroku/brew/heroku

# Ubuntu
curl https://cli-assets.heroku.com/install.sh | sh

2. Login

heroku login

3. Create app

heroku create myapp

4. Add PostgreSQL

heroku addons:create heroku-postgresql:mini

5. Set environment variables

heroku config:set SECRET_KEY_BASE=$(rails secret)
heroku config:set SENTRY_DSN=your_dsn

6. Deploy

git push heroku main

7. Run migrations

heroku run rails db:migrate

Heroku Commands

# View logs
heroku logs --tail

# Open console
heroku run rails console

# Run migrations
heroku run rails db:migrate

# Restart
heroku restart

# Scale dynos
heroku ps:scale web=2

# Open app
heroku open

Part 6: Database Migrations

Migrations must run automatically on deploy or you'll forget.

Fly.io Migrations

Add to Dockerfile or use a release command:

# Dockerfile
FROM ruby:3.2-slim

# ... other config ...

# Run migrations before starting the server
RUN bundle exec rake db:migrate

CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

Or use a separate release command in fly.toml:

[deploy]
  release_command = "bundle exec rake db:migrate"

Render Migrations

Add to your Build Command:

bundle install && bundle exec rake assets:precompile && bundle exec rake db:migrate

Kamal Migrations

kamal app exec -i "rails db:migrate"

Heroku Migrations

Heroku runs rails db:migrate automatically after deploy if you have a release phase in Procfile:

# Procfile
web: bundle exec puma -C config/puma.rb
release: bundle exec rake db:migrate

Part 7: Database Backups

You will lose data eventually. Plan for it.

PostgreSQL Backups

Fly.io: Backups are automatic. Download via:

fly pg backup list
fly pg backup download <id>

Render: Daily backups included. Download from dashboard.

Heroku: PG Backups add-on (free for basic plan).

heroku pg:backups:capture
heroku pg:backups:download

Kamal + VPS: Set up your own cron job:

# /etc/cron.daily/pg_backup
#!/bin/bash
pg_dump myapp_production | gzip > /backups/myapp_$(date +%Y%m%d).sql.gz

Part 8: Asset Precompilation

Rails must compile CSS, JavaScript, and images for production.

Standard Setup

# config/environments/production.rb
config.assets.compile = false  # Always false in production
config.assets.digest = true
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?

Precompile Locally (For Some Deployments)

RAILS_ENV=production bundle exec rake assets:precompile
git add public/assets
git commit -m "Precompile assets"
git push

Precompile on Deploy (Recommended)

Most platforms run:

bundle exec rake assets:precompile

During the build phase.

If Assets Don't Load

Check these:

# Verify files exist
ls public/assets/

# Check Rails is serving them
curl -I https://yourapp.com/assets/application-xxx.css

# If not, set environment variable
RAILS_SERVE_STATIC_FILES=true

Part 9: Environment-Specific Config

Don't hardcode anything. Use environment variables.

Rails Credentials vs ENV

Method Best For
ENV.fetch("KEY") Values that change per environment
Rails.application.credentials Secrets committed to repo

Example Config

# config/database.yml
production:
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  url: <%= ENV["DATABASE_URL"] %>

# config/storage.yml
amazon:
  service: S3
  access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
  secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>
  region: <%= ENV["AWS_REGION"] %>
  bucket: <%= ENV["AWS_BUCKET"] %>

# config/initializers/sentry.rb
Sentry.init do |config|
  config.dsn = ENV["SENTRY_DSN"]
end

Part 10: Post-Deployment Checklist

After your app is live, verify everything works.

Smoke Tests

# Check home page
curl -I https://yourapp.com

# Check health endpoint
curl https://yourapp.com/up

# Check static assets
curl -I https://yourapp.com/assets/application.css

# Check database
curl https://yourapp.com/api/health/db

Monitoring to Set Up

Tool Purpose Free Tier
Sentry Error tracking 5,000 events/month
Scout Performance monitoring Trial only
Uptime Robot Uptime monitoring 5 monitors free
Logtail Log management 5GB/month
Better Stack Uptime + logs 1 monitor free

Initial Tasks

# Run migrations if not automatic
rails db:migrate

# Seed data if needed
rails db:seed

# Precompile assets if not automatic
rails assets:precompile

# Warm up caches
rails runner "Product.preload_categories"

The Deployment Comparison Table

Feature Fly.io Render Heroku Kamal
Free tier Yes No Yes (sleeps) N/A
Starting price $5 $7 $25 $10 (VPS)
SSL Auto Auto Auto Manual
PostgreSQL Optional (extra $5) Included Add-on ($9) Self-managed
Redis Optional Optional Add-on Self-managed
Global deployment Yes No No Optional
Zero downtime Yes Yes Yes (paid) Yes
Console access fly ssh console Built-in heroku run kamal app exec
Learning curve Low Very Low Low Medium

My Recommendation

Start with Fly.io if you want global deployment and low cost.

Start with Render if you want the simplest possible setup.

Use Kamal when you need control and have the skills.

Use Heroku only if you already know it or need its add-ons.


Quick Start Commands

Fly.io (What I recommend for most)

fly launch
fly postgres create
fly secrets set SECRET_KEY_BASE=$(rails secret)
fly deploy

Render

Push to GitHub → Connect on render.com → Add PostgreSQL → Deploy

Kamal (For your own server)

kamal init
kamal server bootstrap
kamal deploy

Summary

Deploying Rails doesn't have to be hard.

Start simple. Use Fly.io or Render. They handle SSL, databases, and scaling.

Don't spend weeks researching deployment platforms. Pick one. Deploy. Get back to building your app.

Your users don't care where you host. They care that your app works.

Make it work. Make it fast. Make it reliable.

Then go build the next thing.