How to Deploy Ruby on Rails 8 to Production with Kamal 2 & Docker (2026 Playbook)
Kamal 2 is the default deployment tool for Rails 8. Here is the complete production playbook: zero-downtime rolling deploys, SSL auto-renewal, Docker image optimization, PostgreSQL accessories, and disaster recovery.
Heads up: some links in this post are affiliate links. If you sign up through them, we may earn a small commission at no cost to you. We only recommend tools we'd use on our own client projects.
Why Kamal 2 is the Standard for Rails 8 Production Deployments
For over a decade, Rails teams faced a painful dilemma: pay exorbitant monthly bills for PaaS platforms like Heroku and Render, or spend weeks wrestling with complex Kubernetes and Terraform setups. Kamal (created by DHH and 37signals to power Basecamp and HEY) changed the entire deployment equation.
Kamal 2 replaces Traefik with a lightweight, specialized Kamal Proxy written in Go, eliminating external dependencies and providing instant, sub-millisecond container swapping with zero dropped HTTP requests during rolling updates.
| Deployment Model | Monthly Cost (100k users) | Deploy Speed | Zero-Downtime | Vendor Lock-in |
|---|---|---|---|---|
| Kamal 2 on Hetzner/VPS | $20 – $50/mo | 45–90 seconds | Yes (Kamal Proxy) | None (Any Linux VPS) |
| Heroku Standard / Performance | $250 – $700/mo | 2–4 minutes | Yes | High (Buildpacks) |
| AWS ECS / Fargate | $150 – $400/mo | 3–6 minutes | Yes (ALB) | Moderate (AWS IAM) |
| Kubernetes (EKS/GKE) | $300 – $900/mo | 2–5 minutes | Yes (Ingress) | Low (Complex Ops) |
1. Production Multi-Stage Dockerfile for Rails 8
A lean Docker image is critical for rapid deployments. The multi-stage Dockerfile below compiles native gems and assets in a throwaway build layer, producing a final production runtime image under 200MB:
# syntax = docker/dockerfile:1
ARG RUBY_VERSION=3.3.4
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
WORKDIR /rails
ENV RAILS_ENV="production" BUNDLE_DEPLOYMENT="1" BUNDLE_PATH="/usr/local/bundle" BUNDLE_WITHOUT="development:test"
FROM base AS build
RUN apt-get update -qq && apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config
COPY Gemfile Gemfile.lock ./
RUN bundle install && rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git
COPY . .
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
FROM base
RUN apt-get update -qq && apt-get install --no-install-recommends -y curl libpq5 libvips && rm -rf /var/lib/apt/lists /var/cache/apt/archives
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
RUN groupadd --system --gid 1000 rails && useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && chown -R rails:rails db log storage tmp
USER 1000:1000
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]
2. Complete Battle-Tested config/deploy.yml
Kamal 2 organizes your web server, background workers (Solid Queue), database accessories, and SSL certificates in a single clear YAML specification:
service: my_app
image: myusername/my_app
servers:
web:
hosts:
- 192.0.2.1
labels:
traefik.enable: false
proxy:
ssl: true
host: app.example.com
app_port: 80
healthcheck:
path: /up
interval: 3
timeout: 2
job:
hosts:
- 192.0.2.1
cmd: bin/jobs
registry:
server: ghcr.io
username: myusername
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- RAILS_MASTER_KEY
- DATABASE_URL
accessories:
db:
image: postgres:16-alpine
host: 192.0.2.1
port: 5432
env:
clear:
POSTGRES_DB: my_app_production
POSTGRES_USER: my_app
secret:
- POSTGRES_PASSWORD
directories:
- data:/var/lib/postgresql/data
3. Step-by-Step Initial Setup & Zero-Downtime Deploy Workflow
Follow this exact sequence to deploy your Rails application to a fresh Ubuntu 22.04 / 24.04 VPS:
- Provision your VPS: Spin up a VPS on Hetzner ($6/mo for 4GB RAM) or AWS EC2, and point your DNS A-Record (e.g.
app.example.com) to the server IP. - Set Up SSH Authentication: Copy your local public SSH key to the server using
ssh-copy-id root@192.0.2.1. - Configure Secrets: Create
.env.kamalwith your Docker registry credentials andRAILS_MASTER_KEY. - Run Initial Setup: Execute
kamal setup. Kamal will install Docker, configure Kamal Proxy, set up PostgreSQL accessories, build the image, and boot the application. - Deploy Future Updates: Simply run
kamal deploywhenever you push code changes. Kamal performs a zero-downtime rolling switchover with automatic health checks against/up.
Zero-Downtime Database Migration Best Practices
When deploying database migrations, never run blocking ADD COLUMN or CHANGE COLUMN locks on large tables. Kamal executes migrations during the deploy step using:
# Automatically executed by Kamal before container switchover
bin/rails db:prepare
Always follow the Expand and Contract pattern: first deploy the migration adding the new column/table, then deploy code that writes to both old and new columns, and finally drop the old column in a subsequent release.
Need Expert Help Deploying or Migrating to Kamal 2?
Our DevOps and Rails engineers migrate legacy applications from Heroku and AWS to Kamal 2, cutting cloud infrastructure costs by 70% while improving deploy speeds.
Get a Free Deployment Estimate →Frequently Asked Questions
How does Kamal 2 achieve zero-downtime deployments?
Kamal 2 starts the new container version alongside the old one, waits for the health check at /up to return HTTP 200 OK, instructs Kamal Proxy to redirect incoming TCP connections to the new container, and then gracefully stops the old container.
Can Kamal 2 run multiple applications on a single VPS?
Yes. Kamal Proxy routes traffic based on hostnames. You can run multiple distinct Rails applications or staging/production environments on a single $20/month VPS by configuring unique host domains.
How are SSL certificates managed in Kamal 2?
Kamal Proxy has native Let's Encrypt ACME support built in. Setting ssl: true and host: app.example.com automatically requests, installs, and renews TLS/SSL certificates with zero manual intervention.
What happens if a deployment fails the health check?
If the new container fails the health check at /up, Kamal aborts the deployment immediately. Kamal Proxy continues routing 100% of user traffic to the existing healthy container, ensuring zero customer downtime.