How to Deploy a Ruby on Rails App (Rails 8)
Rails runs well on Chajio Cloud: your gems are installed, your assets precompiled and Puma started on two copies, next to a managed PostgreSQL database. A fresh Rails 8 app needs four small changes first, because it expects four databases and a worker process where the platform gives you one database and a web process. This guide makes each change, explains why, and shows what a healthy first deploy looks like.
On this page
Before you start
- A Chajio Cloud App Hosting plan. The Free plan is enough to follow along. Once you order, you get an email with your sign-in details, and the first time you sign in you choose your own password.
- Your code on GitHub, in a repository you own or can access. A public repository you don't own works too.
- A Rails app that uses PostgreSQL, such as one made with
rails new myapp --database=postgresql, withGemfile.lockcommitted. SQLite, therails newdefault, can’t keep data here, as the last section explains.
Step 1: Get your app ready
Delete the generated Dockerfile
rails new adds a Dockerfile. When a repository has one, the platform builds it as written, and this one is slow: in our tests it took 35.6 minutes with a build machine to itself, and twice ran past the 45-minute build limit while other builds were running. Without it, the platform builds Rails itself in about six minutes, and the app behaves the same.
git rm DockerfileIf you keep it anyway, the changes below still apply, except the Procfile and the Ruby version: the Dockerfile’s entrypoint runs db:prepare on every start, and its first line picks the Ruby version.
Add a Procfile
A file named Procfile, no extension, in the root of your repository says how to start your app. This one prepares the database, running any new migrations, then starts Puma on the platform’s port:
web: bin/rails db:prepare && bin/rails server --port "$PORT"Without a Procfile your app still starts, but nothing runs your migrations, and its tables go missing.
Pin your Ruby version
The build reads the Ruby version from Gemfile.lock, not from .ruby-version. Point your Gemfile at the file rails new created:
source "https://rubygems.org"
ruby file: ".ruby-version"Then run bundle lock and commit Gemfile.lock. It gains a RUBY VERSION section, and the build log confirms it with Ruby version 3.4.12 from Gemfile.lock. Without it, your app builds on a default Ruby that may not be the one you develop on.
Use one database
In production, a new Rails 8 app expects four PostgreSQL databases: one for your data and one each for Solid Cache, Solid Queue and Solid Cable. A managed database is one database, owned by your app’s user, who can’t create others. Point production at it through DATABASE_URL, which the platform sets when you add the database:
production:
<<: *default
url: <%= ENV["DATABASE_URL"] %>Move Solid Cache, Queue and Cable into it
This is the single-database setup the Solid gems document. Each of them ships a schema file, db/cache_schema.rb, db/queue_schema.rb and db/cable_schema.rb. Turn each one into a migration:
bin/rails generate migration CreateSolidCacheTables
bin/rails generate migration CreateSolidQueueTables
bin/rails generate migration CreateSolidCableTablesclass CreateSolidCacheTables < ActiveRecord::Migration[8.1]
def change
# Paste everything between `ActiveRecord::Schema[…].define(version: 1) do`
# and the final `end` of db/cache_schema.rb here.
end
endDelete the three schema files once their contents are in migrations, and run bin/rails db:migrate on your computer to check the migrations apply. Then remove the three settings that send each gem to its own database:
# config/environments/production.rb
config.solid_queue.connects_to = { database: { writing: :queue } }
# config/cache.yml, under production:
database: cache
# config/cable.yml, under production:
connects_to:
database:
writing: cableCommit and push. The last two changes, a secret key and the job runner, are settings rather than code, and you make them in Step 5.
Step 2: Create a project
A project is the home for one app, along with its database and domain. In your dashboard, open Projects and click New project:
- Name
- Lowercase letters, numbers and hyphens, up to 24 characters. It becomes part of your app’s free web address and can’t be changed later, so pick something short, like shop or api.
- Plan
- The plan the project draws its resources from.
- Resources
- CPU, memory and storage for this project, pre-filled with what your plan has left. The defaults suit most apps.
Click Create project. Setting it up takes about a minute, and you land on the deploy guide straight away, which walks you through three short steps: Source, Configure and Deploy. You can pick your repository while the project finishes setting up.
Step 3: Add a PostgreSQL database
Your app needs its database from the very first start, so create it before you deploy. Open the project’s Databases tab, click Add database, choose PostgreSQL, and enter DATABASE_URL under Connect your app. It takes two to five minutes to set up; wait until its card shows it is ready, then click Deploy at the top of the project to carry on. The full walkthrough is in How to Add a Database to Your App.
Step 4: Connect your repository
On the Source step, choose where your code comes from:
- Your repository
- Click Connect GitHub and approve access, which lets Chajio Cloud read your code and hear about your pushes. Then choose the repository and the branch to deploy, usually main. From now on, every push to that branch deploys automatically.
- Public repository
- Paste the URL of any public GitHub repository, no GitHub sign-in needed. Since you don’t own it, pushes can’t trigger deploys: deploy by hand, or tick Auto-deploy new releases to follow the project’s published releases.
If your app lives in a subfolder of the repository, as in a monorepo, set Build context to that folder, for example apps/web. Otherwise leave it as ., the repository root. Then click Connect repository and Continue.
Step 5: Configure
The Configure step has two runtime settings, both changeable later without a rebuild, and your environment variables.
- Container port
- Leave it at 3000. Your Procfile starts Rails on PORT, so it follows whatever you set here.
- Health check path
/up, the health route every new Rails app has. This one matters: a new app has no home page in production, so the default,/, returns 404 and the deploy never becomes ready.
Add two variables:
SECRET_KEY_BASE=paste-the-output-of-bin/rails-secret
SOLID_QUEUE_IN_PUMA=trueSECRET_KEY_BASEsigns your app’s cookies. Generate one withbin/rails secret. SettingRAILS_MASTER_KEYto the contents ofconfig/master.keyworks instead. Set one of them yourself, so the key stays the same from one deploy to the next.SOLID_QUEUE_IN_PUMAruns your background jobs inside the web server. Apps here have a web process and no separate worker, so without it jobs are queued and nothing ever runs them.
DATABASE_URL is already listed, locked, because the platform manages it for you.
Variables live on the project's Environment tab. Add each one as a name and a value, then click Save & restart: your app restarts with the new values in moments, no rebuild needed.
Your build receives the variables too. A Dockerfile gets the ones it declares with an ARG line, and an auto-detected build gets all of them. A variable that went into your current build is marked Build. Change one of those and the dashboard offers to Redeploy, because only a new build picks up the new value.
PORT is set by the platform from the container port, so you can't set it yourself. If you attach a database, its connection variable appears here too, locked, because the platform manages it for you.
Click Save and continue.
Step 6: Deploy
The Deploy step shows a summary of the repository, branch and build context. Click Deploy now and the build log starts streaming: your code is fetched, dependencies are installed and your app is built and started. The deployment moves through Queued, Building and Deploying to Success.
The first build takes a few minutes. Later builds reuse the parts that didn't change, so they are usually quicker. When it finishes, your app is live on a free live.chajio.cloud address with HTTPS already switched on. You'll find the address on the project's Overview.
If the deployment fails, the dashboard names the stage that broke and why, and the build log shows the details. Our troubleshooting guide covers the usual causes.
What a healthy first deploy looks like
The build log shows your gems installing and your assets compiling, then the start command it found:
## Ruby
- Ruby version `3.4.12` from `Gemfile.lock`
- Bundler version `2.6.9` from `Gemfile.lock`
- Bundle install gems
- Running `BUNDLE_WITHOUT="development:test" bundle install`
- Default process detection
- Detected rails app (`rails` gem found)
- Rake assets install
- Running `rake assets:precompile assets:clean --trace`
## Procfile
- Processes from `Procfile`
- web: `bin/rails db:prepare && bin/rails server --port "$PORT"`Then both copies start. In our tests they took 20 to 30 seconds to answer /up, migrations included, and used 170 to 285 MB of memory each with background jobs running in Puma, so allow at least 512 MB per copy.
One crash on the first deploy is normal
Both copies run db:prepare on the empty database at the same moment, and the one that loses stops with ActiveRecord::ConcurrentMigrationError, or with PG::UniqueViolation … schema_migrations_pkey if db/schema.rb is committed. It restarts by itself and was ready about 20 seconds later in our tests. The deploy still succeeds: the platform waits up to five minutes for both copies. Later deploys replace one copy at a time, so it doesn’t happen again.
You don’t need force_ssl or assume_ssl. HTTPS is handled before requests reach your app, and Rails already sees them as secure: request.ssl? is true and request.remote_ip is the visitor’s address. Leave both commented out, as rails new left them.
Connect your domain
Your app already works on its free address. To serve it from your own domain, open the project's Domains tab:
- Enter your domain, for example
www.example.com, and click Add domain. - The dashboard shows two DNS records to add where your domain's DNS is managed. A TXT record proves the domain is yours. A CNAME record routes visitors to your app, or an A or ALIAS record for a root domain like
example.com. Copy the values exactly as shown. - Click Verify. Once both records check out, a free HTTPS certificate is issued automatically, usually in under a minute, and your app is live on your domain.
DNS changes can take a while to spread, so if Verify doesn't find a record yet, give it a few minutes; the dashboard also re-checks on its own. The full walkthrough, including root domains and Cloudflare, is in How to connect your domain to your app.
If you turn on config.hosts in config/environments/production.rb, list your domain and your free address, and keep /up excluded, as the commented host_authorization line does. The health check isn’t addressed to your domain, so a strict host list would otherwise turn it away.
After launch
- Every push deploys. Push to your branch and a new deployment starts on its own. Your current version keeps serving visitors until the new one passes its health check, so updates don't cause downtime. You can also deploy by hand from the Deployments tab.
- Roll back in seconds. If a release misbehaves, open Deployments and click Rollback to this version on an earlier successful deployment. Nothing is rebuilt; the previous version simply comes back.
- Watch it run. The Logs tab streams your app's output live, with secrets hidden. The Overview shows your app's status and address, with Redeploy, Restart and Stop buttons.
Your app runs as two copies
Every plan runs two copies of your app, so a crash or an update never takes it offline. That has one consequence worth designing for: anything an app keeps only in its own memory or on its own disk isn't shared with the other copy, and it's gone after a redeploy.
Sessions in cookies, the Rails default, already work across both copies. Store Active Storage uploads on an S3-compatible service rather than the local disk, which is replaced on every deploy.
The same goes for SQLite: its database is a file inside the app, so it would be lost on every deploy, and each copy would have its own. Keep your data in the managed PostgreSQL database.
Common problems
The app crashes at start with a socket error
ActiveRecord::ConnectionNotEstablished: connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directoryThe cache, queue or cable database still has its own entry in database.yml, so it has no address. Replace the production section and move the Solid gems into one database, as in Step 1.
The app crashes at start asking for a secret
ArgumentError: Missing `secret_key_base` for 'production' environment, set this string with `bin/rails credentials:edit`Set SECRET_KEY_BASE or RAILS_MASTER_KEY on the Environment tab.
The deploy never becomes healthy, and the logs say No route matches [GET] "/"
The health check is asking for a home page your app doesn’t have. Set Health check path to /up.
The first deploy logs ConcurrentMigrationError or schema_migrations_pkey
Both copies set up the database at once. The one that lost restarts by itself and the deploy still succeeds, so there is nothing to do.
The build stops at 45 minutes
It is building the Dockerfile rails new generated. Delete it, add the Procfile and the ruby line in your Gemfile, and push again.
Background jobs are queued but never run
Set SOLID_QUEUE_IN_PUMA=true on the Environment tab so Solid Queue runs inside Puma.
Tables are missing after a deploy
Nothing ran your migrations. Add the Procfile from Step 1, which runs db:prepare before Puma starts.
For anything else, Deployment Failed? How to Find and Fix the Cause explains how to read a failed deployment. Plans and a summary of everything here are on the Ruby on Rails hosting page.
Learning path
Deploy your first app
- 1
- 2
- 3
- 4
Keep reading
How to Add a Database to Your App
Create a managed PostgreSQL, MariaDB or MongoDB database next to your app and have its connection details handed to your app automatically, with examples for Node.js, Python, Django, .NET and Rails.
How to Connect Your Domain to Your App (with Free HTTPS)
Put your app on your own domain in three DNS steps. Covers www versus root domains, what each DNS record does, Cloudflare, and what to check when a domain will not verify.
Deployment Failed? How to Find and Fix the Cause
Every failed deployment tells you which stage broke and why. How to read it, and the fixes for the failures we see most: missing start commands, port mix-ups, health checks and memory.
Why Your Website Stays Online Even When It Gets Updated
Ever seen a "We're down for maintenance" page? Here is why that happens, why it does not have to, and what good hosting looks like when updates run quietly in the background.
Host your Ruby on Rails app on Chajio Cloud
Free for your first month, with a managed database and two replicas of your app on every plan.