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 to zero.
Idle Machines Stop, and the Next Request Starts Them Again
Fly Proxy stops Machines that have no traffic and starts them on the next request, and a stopped Machine isn't billed for CPU or RAM. When load grows, one command, fly scale, changes memory, CPU, or the Machine count. Fly Machines boot Rails Docker images in a few seconds directly on our metal servers.
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)
- Automatic encryption of data at rest and in transit
-
Built for HTML Over the Wire Turbo & ActionCable Apps
Fly Proxy sits in front of your Machines, terminates TLS with managed certificates, and load balances your app for you via HTTP/2 with Brotli compression and encrypted WebSockets. Turbo Streams and ActionCable connect through it with no proxy or certificate of your own to run.
-
S3-Compatible ActiveStorage, Set Up in One Command
Tigris is S3-compatible object storage run on Fly.io. fly storage create makes a bucket and sets its credentials as secrets on your app, so ActiveStorage's S3 service reads them from the environment with no keys to copy by hand.
-
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, and fly redis create writes REDIS_URL onto your app as a secret, so there is no connection string to paste.
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, 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).
What you didn't have to set up
Look back at what actually happened. You ran fly launch, then fly mpg create
and fly mpg attach. A standard Rails app with a Postgres database in one region is the ordinary case Fly.io is built for, not a stretch. Here is the work that never appeared:
A hosting service for your code.
fly launch and fly deploy
build from the directory you are standing in. A Rails app with no git remote at all deploys fine.
A Dockerfile.
fly launch detects Rails and writes one, along with the fly.toml. Both land in your repository where you can read them, edit them, and see them in a diff.
A connection string.
fly mpg attach writes DATABASE_URL onto the app as a secret. Your database.yml
does not change, and the password never lands in your clipboard or your shell history.
A migration step in a deploy pipeline.
fly launch writes release_command into fly.toml, so migrations run on a throwaway Machine before any Machine takes traffic. A migration that fails fails the deploy, instead of half migrating production.
TLS and a hostname.
The app answers on <app>.fly.dev
with HTTPS from the first request, and the certificate is issued and renewed for you.
A load balancer or a VPC. Fly Proxy is already in front of your Machines, and your app reaches Postgres over your organization's private network as soon as the cluster is attached.
None of that work is missing. It is done, and the record of it is a fly.toml
you can read in one screen, diff in a pull request, and revert with git.
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'"
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. 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.