This article shows how to deploy a typical Rails application with custom Dockerfile to Fly.io. We’ll deploy Rails and Sidekiq processes as well as PostgreSQL and Redis databases. We’ll also touch on scaling instances or Fly.io specific secrets management.
Fly is an interesting Platform as a service provider who can help you deploy your Rails applications without too much friction.
Most of what we’ll need to do can be accomplish with their command line tool flyctl.
$ curl -L https://fly.io/install.sh | sh
And export the required paths:
# Manually add the directory to your $HOME/.bash_profile (or similar) export FLYCTL_INSTALL="$HOME/.fly" export PATH="$FLYCTL_INSTALL/bin:$PATH"
You can signup for a Fly account directly using the
flyctl command line interface:
$ flyctl auth signup Opening https://fly.io/app/auth/cli/a36796651b11e1bbb06e10d5e17a06df ... Waiting for session... Done successfully logged in as (...)
If you have an account, log in instead:
$ flyctl auth login
Note: The Fly free offering won’t be enough for a regular Rails application, so you should either select one of their plans or pay as you go. They don’t accept debit cards, but you can purchase a pre-paid credit.
There are two ways to configure your application enviroment.
If you need to set an environment variable, you can do so in the
fly.toml configuration file within the
$ cat fly.toml [env] PORT = "8080" MY_ENV = "value" ...
But if you need to keep a secret, Fly comes with its own secret management as an alternative to Rails Credentials.
To list current secrets for the application, run
fly secrets list:
$ fly secrets list
To set a new secret, run
flyctl secrets set:
$ flyctl secrets set MY_SECRET=romance
Rails and PostgreSQL
Fly should be able to detect your Rails application automatically. Run
fly launch from your project directory to start:
$ fly launch pdate available 0.0.395 -> v0.0.425. Run "flyctl version update" to upgrade. Creating app in /home/strzibny/rails/template Scanning source code Detected a Rails app ? Overwrite "/home/strzibny/rails/template/.dockerignore"? No ? Overwrite "/home/strzibny/rails/template/Dockerfile"? No ? Overwrite "/home/strzibny/rails/template/lib/tasks/fly.rake"? No ? App Name (leave blank to use an auto-generated name): Automatically selected personal organization: Josef Strzibny ? Select region: ams (Amsterdam, Netherlands) Created app cold-wave-2722 in organization personal Set secrets on cold-wave-2722: RAILS_MASTER_KEY Wrote config file fly.toml ? Would you like to set up a Postgresql database now? (y/N)
The above walkthrought will let you use your Dockerfile such as the optimized Fullstaq Ruby Dockerfile coming with Business Class kit.
It will also allow you to create your first database. Choose yes (y):
Creating postgres cluster cold-wave-2722-db in organization personal Postgres cluster cold-wave-2722-db created Username: postgres Password: dd5d0cddaab6b3cc5f7df64b515cca1fedda3c841ae935a9 Hostname: cold-wave-2722-db.internal Proxy Port: 5432 PG Port: 5433 Save your credentials in a secure place -- you won't be able to see them again! ... Connect to postgres Any app within the personal organization can connect to postgres using the above credentials and the hostname "cold-wave-2722-db.internal." For example: postgres://postgres:firstname.lastname@example.org:5432 ...
These database credentials will be put automatically to your Fly secrets:
Postgres cluster cold-wave-2722-db is now attached to cold-wave-2722 The following secret was added to cold-wave-2722: DATABASE_URL=postgres://cold_wave_2722:MpQF57Z6L8Nrw5K@top2.nearest.of.cold-wave-2722-db.internal:5432/cold_wave_2722 Postgres cluster cold-wave-2722-db is now attached to cold-wave-2722
If you used a custom Dockerfile, you might need to specify a Ruby version environment if it differs from
.ruby-version. You can do that and other changes within
$ cat fly.toml [env] PORT = "8080" RUBY_VERSION = "3.0.1" ...
You’ll also have to update this file or Fly secrets with any possible environment variables to configure emails or Paddle subscriptions.
After that you can deploy the application with
$ fly deploy ... --> This release will not be available until the release command succeeds. Starting instance Configuring virtual machine Pulling container image Unpacking image Preparing kernel init Starting init (commit: 249766e)... Setting up swapspace version 1, size = 512 MiB (536866816 bytes) Preparing to run: `bin/rails fly:release` as template 2022/10/30 10:15:34 listening on [fdaa:0:cac9:a7b:a27f:9e8d:748b:2]:22 (DNS: [fdaa::3]:53) warning: parser/current is loading parser/ruby30, which recognizes3.0.3-compliant syntax, but you are running 3.0.1. Please see https://github.com/whitequark/parser#compatibility-with-ruby-mri. == 20211211115514 CreateUsers: migrating ====================================== -> 0.0011s I, [2022-10-30T10:15:38.924572 #523] INFO -- : Migrating to CreateTeamRoles (20220113114759) == 20220113114759 CreateTeamRoles: migrating ================================== == 20220113175211 CreateInvitations: migrating ================================ ...
This task builds a Docker image, pushes it to registry, runs migrations and start your application.
Then open your running application in a browser with
$ fly open
Console and Rake tasks
To access your Rails console, first request a token and then run
flyctl ssh console:
$ flyctl auth token $ flyctl ssh console
This way you can also run your Rake tasks, such as the initial task to create an admin account in Business Class:
# cd app && rails admin:create[email@example.com,password,5]
To run arbitrary commands, have a look at the machine run command.
Not every application needs Redis, but Business Class comes with a default Active Job and Action Cable setup that needs Redis installed. To add Redis, run:
$ flyctl redis create ... Your Upstash Redis database sparkling-meadow-6756 is ready. Apps in the personal org can connect to at redis://default:firstname.lastname@example.org If you have redis-cli installed, use fly redis connect to connect to your database. ...
After the instance is provisioned, update the environment with the Redis URL that got printed into the terminal:
$ cat fly.toml [env] PORT = "8080" RUBY_VERSION="3.0.1" REDIS_URL="redis://default:email@example.com"
If you have successfully finished adding up Redis configuration, you should be able to see that Sidekiq Web console can now be opened. But your workers won’t run at this point unless you configure Sidekiq to run embedded or add at least one worker process.
A regular Sidekiq setup comes with at least one worker process, so let’s change our processes configuration to feature two processes:
$ cat fly.toml ... [processes] web = "bundle exec puma -C config/puma.rb" worker = "bundle exec sidekiq" ...
Then change the
app reference to
web within the
$ cat fly.toml ... [[services]] http_checks =  internal_port = 8080 processes = ["web"] ...
Redeploy the application with
fly deploy to see the changes coming alive.
Check that the Sidekiq console now shows more connections than before. That means things are working.
If your application doesn’t have enough resources to run or you need more resources to run the Rails console on the server, you can scale the virtual machine with
$ fly platform vm-sizes ... $ fly scale vm dedicated-cpu-1x
This is the final configuration:
$ cat fly.toml # fly.toml file generated for cold-wave-2722 on 2022-10-30T11:01:24+01:00 app = "cold-wave-2722" kill_signal = "SIGINT" kill_timeout = 5 [processes] web = "bundle exec puma -C config/puma.rb" worker = "bundle exec sidekiq" [build] [build.args] BUILD_COMMAND = "bin/rails fly:build" SERVER_COMMAND = "bin/rails fly:server" [deploy] release_command = "bin/rails fly:release" [env] PORT = "8080" RUBY_VERSION="3.0.1" REDIS_URL="redis://default:firstname.lastname@example.org" [experimental] allowed_public_ports =  auto_rollback = true [[services]] http_checks =  internal_port = 8080 processes = ["web"] protocol = "tcp" script_checks =  [services.concurrency] hard_limit = 25 soft_limit = 20 type = "connections" [[services.ports]] force_https = true handlers = ["http"] port = 80 [[services.ports]] handlers = ["tls", "http"] port = 443 [[services.tcp_checks]] grace_period = "1s" interval = "15s" restart_limit = 0 timeout = "2s" [[statics]] guest_path = "/app/public" url_prefix = "/"
Remember that secrets have to be managed separately using
flyctl secrets set.
This is one of the most minimal Fly.io setups that won’t cost a fortune, but don’t miss on specific Fly features that let you deploy your applications closer to your users. Check Fly official docs on how to do that.
Tip: if you prefer to understand virtual machines deployment in more details, check out Deployment from Scratch.