Articles

Rails recipes, tutorials, and development logs.

Filtered by tag: rails
Clear filter
September 02, 2026 •

The Production-Ready Rails Setup: Hatchbox, Cloudflare, and Edge Optimization

Getting a Rails app running on a clean VPS via Hatchbox takes about ten minutes. But getting that app to run blazingly fast, handle traffic surges without spiking CPU, and eliminate common SSL and caching traps requires an intentional partnership between Hatchbox and Cloudflare. Too often, developers slap Cloudflare in front of Rails in default "Orange Cloud" (proxy) mode and run into edge redirection loops, cache poisoning on session cookies, or bloated Puma workers serving redirect loops for media files. Here is the blueprint for pairing Hatchbox with Cloudflare, along with the exact edge rules, proxy configurations, and caching tweaks needed to extract maximum performance from your server. 1. The SSL Handshake & Redirect FoundationHatchbox provisions Caddy out of the box to manage local SSL via Let’s Encrypt. When you place Cloudflare’s proxy in front of Caddy, you introduce two layers of TLS. • Set SSL to Full (Strict): In Cloudflare under SSL/TLS -> Overview, choose Full (Strict). Never use "Flexible." Flexible forces Cloudflare to speak to Caddy over unencrypted HTTP (port 80). Caddy will issue a 301 back to HTTPS, causing an infinite redirect loop (ERR_TOO_MANY_REDIRECTS). Strict ensures end-to-end encrypted validation against Caddy's certificates. • Apex to WWW (or WWW to Apex) at the Edge: Never make Puma or Rack handle domain rewrites. Offload it entirely to Cloudflare using Redirect Rules (under Rules -> Redirect Rules): • The Prerequisite DNS Record: Cloudflare's edge rules only evaluate if traffic actually reaches Cloudflare. Ensure you have a proxied (Orange Cloud) record for www in your DNS settings, either a CNAME pointing www to @ (your apex domain), or an A record pointing to your server's IP. If this record is missing or gray-clouded, browsers will throw NXDOMAIN before the rule ever executes. • Rule: If Hostname equals "[www.yourdomain.com](https://www.yourdomain.com)" • Action: Dynamic 301 redirect to concat("[https://yourdomain.com](https://yourdomain.com)", http.request.uri.path) • This resolves the canonical redirect at the edge in single-digit milliseconds, saving your VPS from ever handling a useless handshake. 2. Taming Active Storage with Proxy Mode & Edge Cache RulesBy default, Active Storage uses redirect mode: a client requests an asset, your Rails process boots, generates a short-lived signed S3 or bucket URL, and issues a 302 redirect. If you have 50 avatars or attachments on a page, your Puma threads spend all their bandwidth negotiating signed redirects. Enable Proxy Mode in RailsBy default, Active Storage runs in redirect mode: when a client requests an attachment, Puma generates a signed, expiring 302 redirect URL to your storage provider (such as S3 or Backblaze B2). If a page renders 30 images, your Rails server spends compute generating 30 unique signed redirects on every single page load. Instead of hunting down every view to manually swap in rails_storage_proxy_path, tell Rails to route all Active Storage attachments through proxy mode by default. Add this single line to config/environments/production.rb: # config/environments/production.rb config.active_storage.resolve_model_to_route = :rails_storage_proxyNow, standard view calls automatically generate proxy endpoints: <%# Automatically resolves to /rails/active_storage/blobs/proxy/... %> <%= image_tag @article.featured_image %>When proxy mode is active, Rails streams the file directly through your domain and emits long-term, immutable caching directives on binary responses: cache-control: max-age=3155695200, public, immutableThis hands Cloudflare the exact headers needed to cache assets at the edge for months without any manual TTL overrides. Cache Active Storage at the EdgeBecause proxy paths (/rails/active_storage/blobs/proxy/* and /rails/active_storage/representations/proxy/*) serve immutable file contents, you want Cloudflare to cache them permanently so Puma never sees a repeated request. In Cloudflare, go to Caching -> Cache Rules and create an Active Storage Edge Cache rule: • Matching Expression: (http.request.method in {"GET" "HEAD"} and starts_with(http.request.uri.path, "/rails/active_storage/"))• Settings: • Cache Eligibility: Eligible for cache • Edge TTL: Use cache-control header if present, bypass cache if not (or "cache request with Cloudflare's default TTL if not") • Browser TTL: Respect origin TTL Once primed, your VPS CPU stays near 0% when pages with dozens of user-uploaded files load. Why This Works• Zero Compute on Repeat Hits: The first request pulls through Puma, Caddy, and Cloudflare to prime the cache. Every subsequent visit from anywhere in the world serves a sub-20ms edge HIT. • Permanent Browser Caching: Because Browser TTL respects origin headers, client browsers store the file locally across visits (immutable), eliminating unnecessary re-downloads. • No Direct Upload Conflicts: POST requests to direct upload endpoints bypass the rule cleanly. Verifying Edge Performance in DevToolsTo confirm Cloudflare and Hatchbox are communicating correctly: 1. Open Chrome DevTools (Cmd + Option + I or Ctrl + Shift + I) and click the Network tab. 2. Uncheck Disable cache so you can observe real-world browser caching. 3. Reload your page, click on any Active Storage image request, and open the Headers panel: • cf-cache-status: HIT: Cloudflare served the asset from its edge without touching your VPS. • cache-control: max-age=..., public, immutable: Cloudflare preserved your Rails cache headers. • via: 1.1 Caddy: Shows that Hatchbox’s native Caddy reverse proxy handled the upstream origin response when the cache was originally primed. • age: [seconds]: Shows how long the asset has been cached at the edge. 3. Asset Pipeline Edge Offloading (/assets/*)Modern Rails (whether using Propshaft, Sprockets, Vite, or Tailwind CLI) produces fingerprint-digested assets (e.g., application-d893f412.css). Caddy serves these statically from /public/assets, but Cloudflare should absorb 100% of this traffic globally: • Create a Cache Rule for Assets: • Expression: http.request.uri.path starts_with "/assets/" • Cache Eligibility: Eligible for cache • Edge TTL: Override origin -> 1 year • Browser Cache TTL: Override origin -> 1 year Because Rails digests asset filenames whenever file contents change, aggressive 1-year caching will never show stale CSS/JS after a Hatchbox deployment. 4. Protecting Dynamic Rails Routes from Cache LeaksCloudflare's default cache level respects origin Cache-Control headers, but human error in custom controllers can accidentally broadcast private sessions if you enable global caching rules. Ensure you explicitly safeguard authenticated paths by creating a Bypass Rule: • Expression: (http.request.uri.path starts_with "/admin") or (http.cookie contains "_your_app_session")• Action: Bypass cache This ensures that any user carrying a logged-in Rails session cookie always hits the origin Puma worker directly, eliminating edge-caching bugs for logged-in sessions. 5. Caddy & Rails Real IP RestorationBecause every request hits Cloudflare before reaching Hatchbox, your Rails production logs and request.remote_ip calls will show Cloudflare IP addresses by default unless restored. In your Rails configuration: # config/environments/production.rb config.action_dispatch.trusted_proxies = ActionDispatch::RemoteIp::TRUSTED_PROXIES + [ # Trust local reverse proxy (Caddy) IPAddr.new("127.0.0.1"), IPAddr.new("::1") ]Caddy forwards the X-Forwarded-For header automatically, allowing Rails to accurately track user rate-limits, audit logs, and security geolocation. The ResultVector Unoptimized Default Hatchbox + Cloudflare Edge Stack Asset Delivery Puma/Caddy disk I/O on every cold visit Sub-20ms edge HIT globally Active Storage Puma handles 302 redirects per file load Cached at CDN; 0 Rails worker overhead Domain Redirects Full TCP/TLS trip to VPS server Edge 301 resolved before origin ping Compute Cost High memory/Puma thread exhaustion Small $6–$12 VPS comfortably scales By moving static assets, media caching, and redirect logic entirely to Cloudflare's edge, Hatchbox only has to do what Rails does best: render dynamic business logic and process background jobs.

August 26, 2026 •

The PaaS Hangover: Why I Deploy Rails with Hatchbox

Deploying a modern Ruby on Rails application usually forces you into one of two extremes. On one side lies the PaaS route (Heroku, Render, Fly.io). It offers simplicity: push your code, let their buildpacks figure out the rest, and go back to building features. But the convenience tax is brutal. You are locked into arbitrary memory limits, sleep cycles, and per-dyno billing that makes running three small side projects feel like leasing commercial real estate. On the other side is the DIY and containerized route (Kamal, Dokku, or raw VPS orchestration). You get cheap compute, but you also inherit the DevOps maintenance tax. You are suddenly managing Docker registries, debugging multi-stage Dockerfiles, troubleshooting proxy containers, and configuring Let’s Encrypt edge cases. For the vast majority of independent builders and lean software teams, neither extreme makes sense. That is why Hatchbox is my default deployment tool for Rails. The Power of "Bring Your Own Compute"Hatchbox is a control plane, not a hosting provider. You connect your own servers from Hetzner, DigitalOcean, Linode, or AWS via SSH, and Hatchbox handles provisioning, environment configuration, database setups, and zero-downtime deploys. This separation of concerns changes the economics completely. Take a look at some example costs here based on deployment solutions: Platform / Setup Architecture Est. Monthly Cost (3 Small Apps + DBs + Background Workers) Heroku Managed Dynos + Eco Dynos + Postgres Add-ons $70 – $150+ Render / Fly.io Managed Micro-VMs / Containers $45 – $90 Hatchbox + Hetzner/DO 1 Dedicated VPS (4GB RAM, 2 vCPU) + Hatchbox Plan $16 – $24 total With Hatchbox, you pay for raw server hardware once. If you have three low-traffic Rails applications, a handful of cron jobs, and background workers, you can pack them onto a single $6 to $12/month VPS without paying a per-application penalty. Rails-First Sensibility (No Container Friction)Most modern deployment tooling treats Rails as just another generic web service inside a Docker container. Hatchbox treats Rails as a first-class citizen because it was built specifically for it. • Boring, Understandable Infrastructure: Hatchbox provisions native Ubuntu environments with systemd services, Puma, and a fast Caddy reverse proxy. Deploys run using the proven symlink release pattern. • Frictionless Debugging: When an issue crops up, you don't need to jump through Docker namespaces or container exec commands. You SSH into your server, cd into your current release, and run bin/rails console or inspect standard Linux logs. • Modern Rails Defaults: Hatchbox natively supports Rails credentials (RAILS_MASTER_KEY), runs database migrations cleanly on release, handles asset precompilation out of the box, and works out-of-the-box with modern SQLite setups and the Solid Trifecta (Solid Queue, Solid Cache, Solid Cable). The Push-to-Deploy Experience Without the Lock-InHatchbox connects directly to GitHub. Once configured, your workflow looks identical to a high-end platform: 1. Merge your pull request to main. 2. A GitHub webhook triggers the build on your server. 3. Hatchbox runs your bundle, compiles your assets, runs pending migrations, and performs a zero-downtime phased Puma restart. 4. Caddy automatically provisions and renews your SSL certificates. You get the frictionless "push to deploy" developer experience without handing over control of your database or paying an exponential markup on RAM. The Verdict For solo developers, bootstrappers, and small teams, developer cycles are too valuable to spend debugging container orchestration, and server budgets are too lean to hand over to PaaS markup. Hatchbox occupies the exact middle ground: it automates the tedious parts of server administration while keeping your stack standard, fast, and remarkably affordable.