Rails hosting on Fly.io:
convention over configuration for your infrastructure
Build and scale Rails apps on a developer-focused public cloud.
Launch NowReady, Set, Go!
Speedrun Your Rails App Onto Fly.io
Deploy a Rails app to Fly.io in a few minutes with Fly.io's command line tool, flyctl.
> Install flyctl on GNU/Linux
$ curl -L https://fly.io/install.sh | sh
> Run from Rails project root
$ fly launch
> Scale CPU, memory, instances & regions
$ fly scale
Not on GNU/Linux? Install flyctl for your platform.
Up-to-Date with Best Practices
Managed TLS, HTTP/2, Fast launching Docker VMs, Kubernetes & More
Fly.io utilizes the latest standards, open-source tools, and hardware to give Rails developers a modern production environment free from vendor lock-in. We manage it all so you don't have to.
A Secure & Productive Coexistence
WireGuard, Private Networks, TLS & Encrypted Volumes
Each Fly.io app launches by default with its own private network and encrypted volumes so you can focus on building apps instead of wrestling with IAM or writing firewall rules. Your security team will like us.
Scale up, down, or out. Way, way out.
Provision CPU, Memory, and Storage on Servers in 30+ Regions Globally
Use one command to deploy apps to regions close to your users or scale memory, storage, and CPU resources. Fly Machines boot Rails Docker images in a few seconds directly on our metal servers making the stack faster for users and developers
AI-Powered Development
Build Rails Apps with AI Agents
Choose from the leading AI agents for Rails development. Each brings unique strengths to help you build faster and deploy seamlessly on Fly.io.
Claude
Advanced reasoning for complex Rails architectures and MVC patterns.
Cursor
Real-time code assistance with context-aware Rails suggestions.
GitHub Copilot
Seamless GitHub integration with proven Rails development patterns.
Gemini
Multimodal capabilities for Rails apps with rich media processing.
Managed Postgres for Rails Projects
Focus on features and leave database management to Fly.io. Our Managed Postgres service, gives you:
- Automatic backups and recovery
- High availability with automatic failover
- Performance monitoring and metrics
- Resource scaling (CPU, RAM, storage)
- 24/7 support and incident response
- Automatic encryption of data at rest and in transit
-
Built for HTML Over the Wire Turbo & ActionCable Apps
Our infrastructure makes Rail apps fly. We run your apps on servers around the world, close to your users, and load balance it for you via HTTP/2 with Brotli compression and encrypted WebSockets. Web apps running on Fly.io are snappy, like native apps.
-
Globally Distributed ActiveStorage Without the CDN
Tigris Data is an S3-compatible object store with automatic global reach. It intelligently routes data to fit read patterns, minimizing latency. It's the only ActiveStorage backend that keeps files close to your users.
-
Managed Databases & Services
Supabase for Postgres. LiteFS for SQLite. Upstash for a popular key/value store 😉. Fly.io's Omakase menu of fully managed services run on our infrastructure right next to your app keeping latency low and you productive.
What Rails is, and what it's used for
Ruby on Rails is a server-side web application framework written in Ruby. It follows the model-view-controller pattern and ships with conventions for routing, persistence (ActiveRecord), background work, asset compilation, and almost every other piece of infrastructure a web app needs.
The "convention over configuration" idea cuts in two directions. You spend less time wiring things up, and you spend more time learning what Rails has already decided for you. That trade is usually worth it. A team can ship a real product on Rails in days, and the resulting codebase is recognizable to anyone else who has worked on a Rails app before.
Rails powers everything from solo side projects to multi-tenant SaaS at scale. GitHub, Shopify, Basecamp, and a long tail of smaller apps run on Rails. It is not the right tool for every job (a static marketing site does not need an ORM and a job queue), but for an interactive app that owns data and serves authenticated users, Rails removes most of the boilerplate decisions you would otherwise spend a week making.
On Fly.io, Rails runs as a standard Ruby app on Fly Machines, alongside Postgres, Redis, and any other services your app needs. Nothing about the framework is forked or wrapped. The same Rails you run on your laptop runs in production here.
Deploy a Rails app to Fly.io: the actual flow
The flow is three commands: install flyctl, run fly launch, then
fly deploy
for every change after that.
The first command installs the CLI:
curl -L https://fly.io/install.sh | sh
flyctl
is the only tool you need on your machine. Authenticate with fly auth login
once.
The second command kicks off your first deploy:
cd path/to/your/rails/app
fly launch
fly launch walks through a short wizard. It detects that your app is Rails by reading Gemfile and bin/rails, picks a region close to you, generates a Dockerfile, bin/docker-entrypoint, config/dockerfile.yml, and .dockerignore, and writes a fly.toml to your repo. After the wizard finishes, the app builds and deploys, and you get a URL to hit. Provision your database (Managed Postgres) and Redis as separate steps after launch. We cover both in the sections below.
For every change after the first deploy, run fly deploy. That rebuilds the image, pushes it to our registry, and rolls a new version onto your Machines with no downtime. No CI required to get started; you can wire it up later when you're ready.
Deploying an existing Rails app
fly launch
works the same way on an existing Rails codebase as it does on a brand new one. The wizard reads your Gemfile.lock, picks the Ruby version your project pins, and bundles the gems you depend on. It also reads
config/master.key
and stores it as a Fly secret so config/credentials.yml.enc
decrypts at boot.
This is true regardless of how old your app is. A Rails 6 app, a Rails 5 app, even a Rails 4 app will boot if you have a sane
Gemfile.lock
and a config.ru
that Puma can serve. Rails 8 is supported out of the box, since the generated
Dockerfile
uses whichever Ruby version your Gemfile
pins and the base image is updated as new Rubies ship.
What fly launch
cannot do is detect every custom step your existing deploy pipeline runs. If you have a Heroku app with custom buildpacks, npm steps, asset pipelines that depend on environment variables, or release hooks that touch external services, take a few minutes after
fly launch
finishes to review the generated Dockerfile
and fly.toml. The [deploy] release_command
slot in fly.toml
is the right place to add custom pre-boot steps (migrations, asset upload to a CDN, cache warmers, anything else).
Postgres on Fly.io for Rails
The recommended path is Fly.io Managed Postgres (MPG). MPG handles automatic backups and recovery, high availability with automatic failover, performance monitoring, resource scaling, and encryption of data at rest and in transit. We no longer support the older unmanaged fly postgres path; MPG is the supported way to run Postgres on Fly.io.
Provision a cluster and attach it to your app:
fly mpg create
fly mpg list
fly mpg attach your-cluster-id -a your-app-name
fly mpg create walks through plan, region, and volume size. fly mpg list shows the cluster ID. fly mpg attach writes the DATABASE_URL secret onto your app and reboots it. You're now connected.
Your database.yml doesn't change. Run fly deploy after the attach. Migrations run on the next release via the release_command in fly.toml that fly launch sets up for you (see the next section).
The first deploy: handling RAILS_MASTER_KEY and release commands
Two things commonly bite people on the first Rails deploy: a misconfigured RAILS_MASTER_KEY, and migrations that don't run.
The migration story is mostly handled for you. For Postgres apps, fly launch writes a release command into your fly.toml automatically:
[deploy]
release_command = './bin/rails db:prepare'
db:prepare runs migrations if the database exists, and creates plus migrates if it does not. fly deploy runs this release command before any new Machines take traffic, so a fresh schema is in place before requests hit your app. If you see a 500 right after deploy, check fly logs for migration errors before looking elsewhere.
The RAILS_MASTER_KEY issue shows up as 500 errors with ActiveSupport::MessageEncryptor::InvalidMessage in the logs. The master key isn't uploaded automatically by fly launch. Set it as a Fly secret yourself:
fly secrets set RAILS_MASTER_KEY=$(cat config/master.key)
This reboots the app. After it comes up, config/credentials.yml.enc decrypts and your app boots clean.
Running a Rails console against your deployed app
The Fly CLI lets you open a real Rails console against your deployed app in one command:
fly ssh console --pty -C "/rails/bin/rails console"
/rails is the working directory for apps built with the dockerfile generator fly launch uses. --pty attaches a pseudo-terminal so the console is interactive. Database queries hit your production Postgres. Cache operations hit your production Redis. ActiveJob calls enqueue real jobs that real workers pick up.
Be careful what you type. The same access that lets you debug a production issue also lets you User.destroy_all. For destructive operations, prefer a script in bin/ you can read before you run, rather than typing Ruby directly into the console.
For ad-hoc one-off tasks, bin/rails runner works the same way:
fly ssh console --pty -C "/rails/bin/rails runner 'puts User.count'"
Multi-region Rails on Fly Machines
Rails apps deploy in one region by default. To run in more, use fly scale count:
fly scale count 3 --region ord,fra,nrt
That spins up Machines in Chicago, Frankfurt, and Tokyo. Our proxy routes each incoming request to the nearest healthy Machine, so users in Europe hit Frankfurt, users in Asia hit Tokyo, and so on. Fly Machines boot in a few seconds, so traffic spikes scale up by adding Machines rather than queuing requests behind one overloaded process.
The piece this leaves open is the database. A Managed Postgres cluster lives in one region with a primary and a replica for in-region high availability. Cross-Atlantic database reads from Frankfurt back to a Chicago primary add latency that no app-side change can hide.
The pragmatic move for most Rails apps is to keep the database in the region where most of your writes originate, run app Machines globally in front of it, and use caching (Redis, fragment caches, HTTP caching) to absorb hot read paths near the edge. Apps that need globally low-write-latency are a different architecture entirely (LiteFS for replicated SQLite is one option; CRDT-based approaches are another), but for most workloads the "app close to users, database in one place, cache aggressively" model handles the latency story without re-architecting your data layer.
Background jobs: Sidekiq, Solid Queue, Good Job
Background jobs on Fly.io run as ordinary Ruby processes. The standard pattern is to add a second process group to your fly.toml alongside the web one:
[processes]
web = "bin/rails fly:server"
worker = "bundle exec sidekiq"
fly deploy runs your web Machines and your worker Machines from the same image. You scale them independently with fly scale count web=2 worker=4, and you can put them in different regions if it helps your job latency. The bin/rails fly:server task is what fly launch wires up for Rails apps; bundle exec sidekiq (or whichever job system you use) starts the worker.
This pattern works for any Ruby job system. Sidekiq, Solid Queue, Good Job, Resque, and Que all run as processes that read from a backend (Redis or Postgres) and process work. Swap the
worker
command for the one your stack uses.
For Sidekiq specifically, you'll want a shared Redis. The fly redis create
flow provisions a Redis instance (powered by Upstash) and writes a
REDIS_URL
secret. Solid Queue and Good Job both use Postgres as their backend, so they piggyback on your existing
DATABASE_URL
and don't need a separate Redis.
The thing to watch is database connection pool size. Each worker process opens connections to Postgres just like your web processes do. Set
RAILS_MAX_THREADS
low (or use database.yml's pool
setting) so you don't exhaust your database connection limit when you scale workers up.