Skip to content

Kamal or a PaaS? Where to Deploy a Rails 8 App in 2026

TL;DR

For most Rails 8 apps in 2026, Kamal on one or two VPS boxes is the default worth trying first: it ships with Rails, costs a fraction of a PaaS and is now mature. Pick a managed platform when nobody on the team wants to own backups, patching and on-call for the server itself.

Heroku is in maintenance mode and Rails 8 ships with Kamal out of the box. Here is how to decide between your own VPS and a managed platform, with a production-ready deploy.yml.

Published 7 min read

Also available in Português (Brasil)

Share

Two things changed the Rails deployment conversation. On February 6, 2026, Heroku announced it is moving to a "sustaining engineering model": the platform stays supported, but new features stop and new Enterprise contracts for new customers are gone. Meanwhile Kamal, which every new Rails 8 app already includes, reached version 2.12 in June with native OpenTelemetry support for deploy logs.

So the question for a new app, or for a Heroku app looking for its next home, is no longer "which PaaS?". It is "do I want to own a server at all?"

I recently moved this blog to Kamal on a single VPS, with Postgres running as a Kamal accessory and images on Docker Hub. This post is the decision framework I wish I had before starting.

The short answer

  • Kamal on your own VPS if someone on the team is comfortable with Linux, SSH and backups, and you want predictable cost.
  • A managed PaaS (Render, Fly.io, Heroku) if nobody wants to be paged because a disk filled up, or you need managed Postgres with point-in-time recovery from day one.
  • Your own cloud account (ECS, Beanstalk) if you are already on AWS with compliance requirements, VPC peering and a platform team. Kamal can target EC2 too, but the managed AWS services buy you autoscaling and IAM integration.

What Kamal actually gives you

Kamal is not a platform, it is a deploy tool. It SSHes into your hosts, pulls a Docker image you built, starts the new container, waits for the health check and switches traffic through kamal-proxy with zero downtime. kamal-proxy also handles Let's Encrypt certificates.

What you get from Rails 8 out of the box:

  • A production Dockerfile with jemalloc and Thruster in front of Puma
  • config/deploy.yml and .kamal/secrets
  • The /up health check route
  • Solid Queue, Solid Cache and Solid Cable, so you do not need Redis to run jobs, caching or Action Cable

That last point matters more than Kamal itself. A Rails 8 app with the Solid trio needs a web container and a database. That is it. Fewer moving parts is what makes a single VPS realistic.

What a PaaS gives you that Kamal does not

Be honest about this list before choosing:

  1. Managed database. Automated backups, point-in-time recovery, failover, minor version upgrades. With a Kamal accessory, all of this is yours.
  2. OS patching. Someone has to run unattended-upgrades and reboot for kernel fixes.
  3. Horizontal autoscaling. Kamal deploys to the hosts you list. Adding capacity means provisioning a box and editing deploy.yml.
  4. Observability glue. Log drains, metrics and alerting are a checkbox on a PaaS. On a VPS you wire up your APM agent and a log shipper yourself.
  5. No single point of failure by default. One VPS is one machine. If it dies, you are down until you rebuild it.

None of these are hard. They are just work, and on a PaaS someone else does that work for you.

A production deploy.yml

This is close to what runs this blog: one web host, Postgres as an accessory on the same box, Solid Queue inside Puma.

service: myapp
image: myuser/myapp

servers:
  web:
    - 203.0.113.10

proxy:
  ssl: true
  host: myapp.com

registry:
  username: myuser
  password:
    - KAMAL_REGISTRY_PASSWORD

builder:
  arch: amd64

env:
  secret:
    - RAILS_MASTER_KEY
    - POSTGRES_PASSWORD
  clear:
    SOLID_QUEUE_IN_PUMA: true
    DB_HOST: myapp-db
    WEB_CONCURRENCY: 2

volumes:
  - "myapp_storage:/rails/storage"

asset_path: /rails/public/assets

accessories:
  db:
    image: postgres:17
    host: 203.0.113.10
    port: "127.0.0.1:5432:5432"
    env:
      clear:
        POSTGRES_USER: myapp
        POSTGRES_DB: myapp_production
      secret:
        - POSTGRES_PASSWORD
    directories:
      - data:/var/lib/postgresql/data

And .kamal/secrets:

KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
RAILS_MASTER_KEY=$(cat config/master.key)
POSTGRES_PASSWORD=$POSTGRES_PASSWORD

A few details that bite people:

  • DB_HOST: myapp-db: accessories join the kamal Docker network as <service>-<accessory>. Your database.yml should read ENV["DB_HOST"].
  • Bind the database to 127.0.0.1. Publishing 5432 on all interfaces puts Postgres on the internet, and Docker's iptables rules bypass UFW.
  • asset_path keeps old fingerprinted assets available during the switch, so users mid-deploy do not get 404s on CSS.
  • builder.arch: amd64 matters on Apple Silicon. Without it, you build an ARM image for an x86 server. Use a remote builder if local emulation is too slow.

First deploy is kamal setup. After that, kamal deploy. kamal app logs -f and kamal console (if you keep the default aliases) cover daily work.

The parts you now own

If you go the Kamal route, do these on day one, not after the first incident:

Backups off the box. A nightly pg_dump shipped to S3 (or any object storage) is the minimum. Test a restore. A database accessory without off-host backups is a single disk away from losing everything.

Active Storage off the box. The myapp_storage volume is fine for SQLite or a toy app. For user uploads in production, point Active Storage at S3 so the server stays disposable.

Monitoring. Ship app metrics with your APM of choice and add an external uptime check on /up. Kamal 2.12's OpenTelemetry support covers deploy logs, not application telemetry.

Patching. Enable unattended security upgrades and schedule reboots. Keep SSH key-only.

Rebuild drill. Once, provision a fresh VPS and deploy to it from scratch. If that takes an hour, your recovery time objective is an hour.

Cost, roughly

I will not quote exact prices, since they move every few months. The shape is stable: a single mid-range VPS from Hetzner, DigitalOcean or similar typically costs about what one small web dyno plus one small managed database costs on a PaaS, but gives you several times the CPU and RAM. The gap widens as you add workers and memory, because on a PaaS each process type is billed separately and on a VPS they share the box.

The hidden cost is your time. If you bill hourly, two hours a month of server care can erase the savings for a small app.

Leaving Heroku for Kamal: a checklist

  1. Upgrade to Rails 8 (or add Kamal and a Dockerfile to a 7.1+ app with bundle add kamal && kamal init).
  2. Replace Redis-backed pieces with Solid Queue, Solid Cache and Solid Cable if you want a two-container setup. Sidekiq still works fine as a separate Kamal role if you prefer it.
  3. Turn your Procfile into Kamal roles: web, and job with cmd: bin/jobs if you do not run jobs in Puma.
  4. Move config vars into .kamal/secrets (pulled from 1Password, Bitwarden or env, never committed).
  5. pg_dump from Heroku Postgres, pg_restore into the accessory or a managed database.
  6. Lower DNS TTL a day before, deploy, verify, then switch the record.
  7. Keep the Heroku app scaled down but alive for a week as a rollback path.

Where a PaaS still wins

Do not let the Kamal enthusiasm push you into owning servers you do not want. A PaaS is the better call when:

  • You have no one who wants on-call for infrastructure.
  • You need managed Postgres with PITR and HA, and do not want to pay for a separate managed database next to your VPS (which is a perfectly good middle ground, by the way).
  • Traffic is spiky enough that autoscaling saves real money.
  • You need multi-region latency, where Fly.io's model is genuinely hard to replicate with Kamal.

Heroku itself is not going away tomorrow. "Sustaining engineering" means stable, not dead. But for a new app, choosing a platform that has publicly stopped building features is hard to justify when Render and Fly.io are actively developed.

My recommendation

Start new Rails 8 apps on Kamal with one VPS, an off-box backup job and S3 for uploads. Put the database on a managed service the moment losing an hour of data would hurt the business. Move to a PaaS, or to ECS on your own AWS account, when the time you spend on the server costs more than the bill you are avoiding.

Sources

  1. An Update on Heroku (Heroku blog, February 6, 2026) www.heroku.com
  2. Simon Willison on the Heroku sustaining engineering announcement simonwillison.net
  3. Kamal releases (v2.12.0) github.com
  4. Kamal documentation kamal-deploy.org

FAQ

Is Kamal production-ready in 2026?

Yes. Kamal ships with every new Rails 8 app, 37signals runs its own products on it, and version 2.12 added OpenTelemetry deploy logs, deploy lock retries and a stop_timeout option.

Is Heroku shutting down?

No. In February 2026 Heroku moved to a sustaining engineering model: it remains supported, but it is no longer building new features or signing new Enterprise contracts for new customers.

Can I run Postgres with Kamal?

Yes, as an accessory container. Bind it to 127.0.0.1, connect through the kamal Docker network hostname, and ship pg_dump backups off the server. For critical data, a managed database is safer.

Do I need Redis to deploy Rails 8 with Kamal?

No. Solid Queue, Solid Cache and Solid Cable use your database, so a web container plus Postgres is enough. Sidekiq and Redis still work as extra Kamal roles and accessories.

Comments

Sign in with GitHub to comment.

No comments yet.