Justin McKelvey
Fractional CTO · 15 years, 50+ products shipped
Why I Replaced $2,200/Year in SaaS with a Single Rails App
TL;DR: $184/Month to $25/Month
I was paying $184/month — $2,208/year — for four SaaS tools I barely used to their full potential. Squarespace for three websites ($48/month). ConvertKit for an email list with 400 subscribers ($33/month). GoHighLevel for a CRM I used 10% of ($97/month). Metricool for social scheduling ($18/month). I replaced all of it with a single Rails 8 application hosted on Railway for $25/month. Annual savings: $1,908. But the real win wasn't the money — it was having everything in one codebase that I own and control.
What I Was Paying For (And What I Actually Used)
The SaaS bloat crept up slowly, the way it always does. Each tool solved a real problem when I signed up. But over time, I was paying for enterprise features I'd never touch while juggling four separate dashboards, four separate logins, and zero integration between them.
Squarespace: $48/month ($576/year)
Three websites on the Business plan. The sites were essentially brochure pages with a blog — nothing that required Squarespace's drag-and-drop builder. I was paying for a visual editor I didn't need because I can write HTML faster than I can drag blocks around. The blog had no SEO optimization beyond basic meta tags. No structured data, no FAQ schema, no llms.txt for AI discoverability.
ConvertKit: $33/month ($396/year)
An email list with 400 subscribers. ConvertKit is genuinely good software, but at 400 subscribers, I was paying for scale I didn't have. The free tier covers up to 10,000 subscribers — I was on a paid plan for features I used once (automation sequences) and paying monthly for the privilege.
GoHighLevel: $97/month ($1,164/year)
This was the biggest waste. GoHighLevel is a CRM, marketing automation, and sales pipeline tool designed for agencies managing dozens of clients. I'm a solo consultant managing 3-4 clients. I used the contact management and pipeline view. The other 90% of the platform — SMS campaigns, landing page builder, reputation management, appointment scheduling with staff management — sat untouched.
Metricool: $18/month ($216/year)
Social media scheduling and analytics. This is the one tool I actually kept — it covers LinkedIn, Instagram, Facebook, Twitter, Threads, Bluesky, YouTube, and TikTok scheduling. Building API integrations for 8+ social platforms isn't worth the development time. Metricool earns its $18/month.
What I Built to Replace Them
The replacement app covers everything the three eliminated tools did, plus features none of them offered.
Website + Blog + CMS (Replaced Squarespace)
Static pages with a rich text editor (Action Text / Trix), SEO meta tags on every page, Open Graph and Twitter Card support, and a blog with GEO-optimized structure — answer-first paragraphs, FAQ sections with JSON-LD schema, auto-generated table of contents, and internal linking across topic clusters. Plus /llms.txt and /llms-full.txt for AI search engine discoverability. None of that existed on my Squarespace sites.
CRM with Pipeline View (Replaced GoHighLevel)
Contact management with a Kanban-style pipeline (drag-and-drop between stages using Turbo Streams). Activity logging for every interaction — emails, calls, meetings, notes. Tag system for segmentation. Follow-up reminders with snooze and complete actions. It does exactly what I need and nothing I don't.
Email Capture + Lead Management (Replaced ConvertKit Capture)
Customizable forms with honeypot spam protection. Auto-creates contacts on submission. Sends confirmation emails via Amazon SES. The email list itself still lives on ConvertKit's free tier (which handles up to 10,000 subscribers) — but the capture mechanism and CRM integration is mine.
Booking System (Bonus — Replaced Calendly)
I didn't even count Calendly in the original cost because I was using their free tier. But the built-in booking system handles availability slots, booking types with configurable duration, calendar conflict detection, and confirmation emails with .ics calendar attachments. One less external dependency.
Content Management + Story Bank (Bonus)
A content organization system that didn't exist in any of the SaaS tools. Topic clusters for blog posts. Content briefs for planned articles. Social media content atomization from blog posts. This is the infrastructure behind the 31 blog posts and 21 social media posts I've published.
The Tech Stack
Every choice was made for simplicity and cost reduction:
Rails 8 — One framework, one language, full stack. No separate frontend app, no API layer, no microservices. Rails 8 ships with everything: authentication generator, Turbo for real-time updates, Stimulus for JavaScript behavior, Action Text for rich text editing, Active Storage for file uploads.
SQLite in production — This is the choice that raises eyebrows. But for a single-server application with one primary user (me), SQLite is faster than Postgres, simpler to manage, and eliminates a $15-50/month managed database service. Rails 8 treats SQLite as a first-class production database.
Solid Queue, Solid Cache, Solid Cable — All three use SQLite as their backing store. This eliminates Redis entirely. Background jobs (email sending, reminder scheduling), caching, and WebSocket connections all run on the same SQLite database. Zero additional infrastructure.
Tailwind CSS — Utility-first CSS with no custom framework to maintain. The entire frontend uses Tailwind classes. No CSS files to debug, no naming conventions to remember.
Amazon SES — Email delivery at $0.10 per 1,000 emails. My monthly email volume is well under 1,000. Cost: roughly $1/month.
Railway — Hosting on the Hobby plan at $5-10/month. Persistent volume for SQLite storage. Auto-deploys from GitHub pushes. SSL certificates provisioned automatically. No Docker Compose, no Kubernetes, no DevOps team.
Litestream — Continuous SQLite backup to Cloudflare R2 (S3-compatible). Every write to the database is replicated in real-time. If the server dies, I restore from backup in under 60 seconds. Cost: $1-2/month for R2 storage.
What Broke Along the Way
I'd be lying if I said the migration was smooth. Here's what went wrong:
Railway volume permissions. The persistent volume mounts at runtime, not build time. Migrations that run during the build step can't write to the database. The fix: set RAILWAY_RUN_UID=0 and run migrations in the entrypoint script. This took 2 days of debugging.
Thruster caused 502 errors. Rails 8 ships with Thruster (an HTTP/2 proxy) enabled by default. On Railway, Thruster's port binding conflicted with Railway's proxy layer. Disabling Thruster and serving Puma directly on port 3000 fixed it immediately. But diagnosing it took a full day because the error looked like a memory issue.
allow_browser blocking crawlers. Rails 8's allow_browser versions: :modern returns 406 for any user agent it doesn't recognize — including every social media crawler (Facebook, Twitter, LinkedIn, Google). My Open Graph previews were broken for weeks before I found the root cause. The fix is a regex exemption for bot user agents.
Content import deduplication. I imported 500+ social media posts from Metricool's API without deduplication logic. Result: duplicate content everywhere. Lesson: always check for existing records by a unique key before inserting. This required a follow-up migration to merge duplicates.
The Real Cost Comparison
ItemBefore (Monthly)After (Monthly) Website/Blog$48 (Squarespace)$0 (included) Email$33 (ConvertKit)$1 (SES) CRM$97 (GoHighLevel)$0 (included) Scheduling$18 (Metricool)$18 (kept) Hosting$0 (included in SaaS)$8 (Railway + S3) Total$196$27Monthly savings: $169. Annual savings: $2,028.
The break-even point on development time was approximately 3 months. After that, every month is pure savings — and the gap grows as SaaS prices increase (which they always do).
What I'd Do Differently
Start with the blog, not the CRM. I built the CRM first because it felt like the most complex feature. But the blog generates all the inbound traffic. If I'd shipped the blog first, I'd have been building SEO authority for months while building the CRM features I use less frequently.
Use Claude Code from day one. I started this project before Claude Code was my primary tool. The last 60% of the build (blog enhancements, SEO infrastructure, content engine) went 3-5x faster than the first 40% because I was using AI-assisted development. If I'd had Cursor and Claude Code from the start, the 6-week build would have been 3 weeks.
Don't import historical data on day one. Get the new system working with new data first. Import history later, with proper deduplication, as a separate task. Mixing a data migration with a feature launch doubles the debugging surface.
Should You Do This?
Honestly? Probably not. This made sense for me because:
1. I'm a Rails developer. The build cost was my time, not contractor fees.
2. I enjoy building tools. This is fun for me, not a chore.
3. My SaaS costs were high relative to my usage. $97/month for a CRM I used 10% of is wasteful.
4. I wanted to own my data and my platform. No more vendor lock-in anxiety.
If you're a non-technical founder, the math is different. Your time is better spent on sales, fundraising, and product strategy — not building internal tools. Use the SaaS tools. The $200/month is well-spent if it saves you 20 hours of development time.
But if you're technical, if your SaaS bill is growing faster than your revenue, and if you're tired of paying for features you don't use — Rails 8 with SQLite is genuinely good enough for solo businesses in 2026. The "you need managed Postgres" advice is outdated for this use case.
For a deeper dive on SQLite in production, read that guide. If you're thinking about building your own tools with AI assistance, the vibe coding tools guide covers what works. And if you'd rather have a fractional CTO build it for you, that's what I do.
Questions about the stack or the migration? Book a strategy call — happy to walk through the details.
Frequently Asked Questions
- Can you really replace SaaS tools with a custom app?
- Yes, if your needs are standard. I replaced Squarespace (website/blog), ConvertKit (email), GoHighLevel (CRM), and Metricool (content scheduling) with one Rails 8 app. Total cost went from $184/month to $25/month. But this only makes sense if you're technical enough to maintain it or have a developer who can.
- Is SQLite really production-ready?
- For single-server applications with one primary user (like a consulting business), SQLite in production works perfectly in 2026. Rails 8 ships with SQLite support including Solid Queue, Solid Cache, and Solid Cable — eliminating the need for Redis entirely. I back up continuously to S3 via Litestream.
- How much does it cost to host a Rails app?
- Railway hosting costs $5-10/month on the Hobby plan. Add ~$2/month for S3 backups and ~$1/month for Amazon SES email delivery. Total infrastructure cost: $8-13/month for a full-stack application with database, background jobs, caching, and email.
- How long does it take to build a SaaS replacement?
- My initial build took 6 weeks of evenings and weekends. That covered the website, blog, CRM, booking system, email capture, and content management. Each feature took 3-5 days. The total time investment was roughly 150-200 hours.
- What are the risks of replacing SaaS with a custom app?
- The main risks are: maintenance burden (you're responsible for updates and bug fixes), no support team (you are the support team), uptime responsibility (no SLA from a vendor), and opportunity cost (time building infrastructure vs. building your actual business). It's worth it only if the SaaS tools don't fit your workflow or the cost savings justify the time investment.
- What tech stack did you use?
- Rails 8 with SQLite (production), Solid Queue for background jobs, Solid Cache for caching, Solid Cable for WebSockets, Tailwind CSS for styling, Hotwire (Turbo + Stimulus) for interactivity, Amazon SES for email, Railway for hosting, and Litestream for continuous SQLite backup to S3. No Redis, no Postgres, no separate frontend framework.