Building Multi-Tenant SaaS on Rails 8: Kamal 2, SSL & Custom Domains
Building a multi-tenant SaaS on Rails 8 in late 2026 requires leveraging Kamal 2, dynamic wildcard routing, and automated Let's Encrypt SSL provisioning via Kamal-proxy. This modern stack eliminates expensive PaaS pla...
Direct Answer: Building Multi-Tenant SaaS on Rails 8
Building a multi-tenant SaaS on Rails 8 in late 2026 requires leveraging Kamal 2, dynamic wildcard routing, and automated Let's Encrypt SSL provisioning via Kamal-proxy. This modern stack eliminates expensive PaaS platforms like Heroku, enabling scalable database-per-tenant or schema-based isolation directly on bare-metal or cloud VMs for a fraction of the cost.
Architectural Foundations of Multi-Tenancy in Rails 8
Multi-tenancy architectures generally fall into three patterns: database-per-tenant, schema-per-tenant, and row-level security (pooled). For enterprise B2B SaaS, the database-per-tenant approach provides the ultimate security and data isolation guarantees, while schema-per-tenant strikes an optimal balance between resource utilization and operational complexity.
In Rails 8, multi-tenancy is natively supported through connection management extensions and the robust Active Record connection switching API. Let's look at how we configure a dynamic tenant switcher using middleware to intercept requests, extract the subdomain, and route database connections securely.
# app/middleware/tenant_elevator.rb
class TenantElevator
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
subdomain = extract_subdomain(request.host)
if subdomain.present? && subdomain != "www"
tenant = Tenant.find_by!(subdomain: subdomain)
Current.tenant = tenant
ActsAsTenant.with_tenant(tenant) do
@app.call(env)
end
else
@app.call(env)
end
rescue ActiveRecord::RecordNotFound
[404, { "Content-Type" => "text/plain" }, ["Tenant not found"]]
end
private
def extract_subdomain(host)
parts = host.split(".")
# Assumes app.com is 2 parts, tenant.app.com is 3 parts
parts.size > 2 ? parts.first : nil
end
end
To register this middleware in your Rails 8 application, update your config/application.rb file:
# config/application.rb
module TechVintaSaaS
class Application < Rails::Application
config.load_defaults 8.0
config.middleware.use TenantElevator
end
end
Automated Deployment with Kamal 2 and Kamal-Proxy
Kamal 2 revolutionized zero-downtime deployments by introducing advanced proxy capabilities, native multi-accessory support, and built-in Let's Encrypt SSL provisioning via kamal-proxy. Unlike traditional reverse proxies (Nginx/Caddy) that require manual certificate management, Kamal 2 automates TLS handshakes and wildcard domain validation at the edge.
Below is a production-grade config/deploy.yml optimized for a multi-tenant Rails 8 application managing dynamic custom domains:
# config/deploy.yml
service: techvinta-saas
image: registry.digitalocean.com/techvinta/saas-core
servers:
web:
hosts:
- 165.232.32.10
options:
ports:
- "443:443"
- "80:80"
proxy:
ssl: true
host: app.techvinta.com
proxy_args: "--tls-calets"
healthcheck:
path: /up
interval: 2
timeout: 3
registry:
server: registry.digitalocean.com
username:
- KAMAL_REGISTRY_USERNAME
password:
- KAMAL_REGISTRY_PASSWORD
env:
clear:
DB_HOST: 165.232.32.11
RAILS_MAX_THREADS: 5
secret:
- RAILS_MASTER_KEY
- DATABASE_URL
Handling Wildcard and Custom Domains at Scale
Enterprise SaaS clients demand custom domain mapping (e.g., app.clientcompany.com). Achieving this with Kamal 2 requires dynamic proxy configuration updates without triggering full container restarts. Kamal-proxy exposes an internal API that allows your Rails application to programmatically bind new domains and trigger SSL certificate generation via ACME protocols.
When a tenant updates their custom domain in your Rails admin panel, invoke a background job to register the domain with the proxy daemon:
# app/jobs/provision_custom_domain_job.rb
class ProvisionCustomDomainJob < ApplicationJob
queue_as :default
def perform(tenant_id)
tenant = Tenant.find(tenant_id)
domain = tenant.custom_domain
# Interact with Kamal-proxy API or local socket
uri = URI("http://localhost:3000/proxy/domains")
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/json' })
request.body = { domain: domain, backend: "http://web:3000" }.to_json
response = http.request(request)
unless response.is_a?(Net::HTTPSuccess)
raise "Failed to provision SSL for #{domain}: #{response.body}"
end
end
end
2026 Architecture & Cost Comparison
Choosing the right stack impacts your engineering overhead, hosting bills, and time-to-market. The matrix below contrasts a modern Rails 8 + Kamal 2 architecture against legacy PaaS and alternative frameworks.
| Metric / Feature | Rails 8 + Kamal 2 (Bare Metal/Cloud) | Legacy Heroku / Render PaaS | Custom Node.js / Kubernetes | No-Code / Sharetribe ($8k-$25k) |
|---|---|---|---|---|
| Infrastructure Cost (Monthly) | $40 - $120 (Scales with RAM) | $300 - $1,200+ (Add-on heavy) | $500 - $2,000+ (DevOps overhead) | $199 - $500/mo subscription |
| SSL & Wildcard Automation | Native via Kamal-proxy & Let's Encrypt | Managed (Expensive tier upgrades) | Requires cert-manager / Traefik | Platform restricted |
| Engineering Hourly Rate | $35 - $65/hr (TechVinta Tier) | $35 - $65/hr | $90 - $150/hr (K8s specialists) | N/A (Restricted customization) |
| Data Isolation Security | Strict Schema / DB per tenant | Application-level filtering (Row security) | Configurable Kubernetes Namespaces | Shared multi-tenant database |
| Deployment Speed | < 90 seconds (Docker push & swap) | 2 - 5 minutes (Git push) | 10 - 20 minutes (CI/CD pipeline) | Instant visual builder |
Accelerate Your SaaS Delivery with TechVinta
Architecting a production-ready, multi-tenant SaaS application with custom domain routing and automated SSL demands deep DevOps and Rails expertise. At TechVinta, our elite engineering team specializes in scaling modern Ruby on Rails applications using Kamal 2, Docker, and cutting-edge cloud infrastructure. We operate with a seamless 4 to 6-hour US timezone overlap, ensuring real-time collaboration, rapid code reviews, and agile sprint execution for North American startups and scale-ups. Contact TechVinta today to accelerate your product roadmap.
Frequently Asked Questions
How does Kamal 2 handle SSL certificate renewal for hundreds of tenant custom domains?
Kamal 2 integrates directly with Let's Encrypt ACMEv2 challenges through kamal-proxy. When a custom domain is registered, the proxy daemon automatically requests, stores, and handles background certificate renewal cycles prior to expiration, eliminating manual certificate maintenance.
Is a database-per-tenant strategy better than row-level security in Rails 8?
Database-per-tenant offers absolute data isolation and simplifies regulatory compliance (GDPR/HIPAA), whereas row-level security (using gems like acts_as_tenant) is more cost-effective for high-density, low-tier SaaS products. Rails 8 natively supports connection switching, making database-per-tenant orchestration much cleaner than in previous versions.
How does TechVinta ensure zero-downtime deployments during schema migrations?
We implement robust backward-compatible database migration patterns (expand-and-contract pattern) combined with Kamal 2's rolling container swaps. This ensures that ongoing tenant requests never fail or timeout while database schemas are updated in production.