I spent about a year building a feature flags SaaS from scratch. No co-founder, no VC, just me, Go, and a Hetzner box that costs less than my monthly coffee. This is the long version of how it went: the stack, the features that took way longer than I expected, the things I got wrong, and the actual numbers. If you're thinking about building your own dev tool solo, maybe some of this saves you a few weekends.
How I Built Rollgate: From Side Project to Feature Flags SaaS
Rollgate is a platform for managing feature flags. I wanted the same things LaunchDarkly gives you (gradual rollouts, user targeting, A/B testing, kill switches) without the enterprise invoice that can hit $70k a year.
The Problem
If you've shipped in a team, you know the spot. The feature's ready, you're fairly sure it works, but "fairly sure" across every user and edge case is doing a lot of lifting. Without flags, a deploy is all-or-nothing. It's live for everyone or for no one.
Flags split deployment from release. The code ships to production turned off, then you ramp it: 1% of users, then 5%, then a quarter, watching as you go. If it misbehaves, you flip it off. No revert commit, no hotfix at 2am.
LaunchDarkly does all of this, and does it well. It also bills somewhere between $25k and $150k a year, which is a non-starter for most startups and mid-size teams. So the question I kept circling back to was whether one person could build something close and sell it for a fraction of that.
Choosing the Tech Stack
The stack came first. Two things mattered more than anything else. Latency, because flags get evaluated on basically every request, and operational simplicity, because I'm the only one on call and fewer moving parts means fewer 3am pages.
Backend: Go
The API is Go. A flag lookup has to finish in microseconds, and Go compiles to a native binary with no GC pauses worth worrying about, chewing through thousands of concurrent connections on goroutines without much fuss. Deployment is a single static binary, no runtime to install, and the final Docker image is 20MB. The standard library covers most of what I need (HTTP, JSON, crypto, testing), so I'm not pulling in a dependency for every little thing.
Routing is Chi. It's thin and plays nice with net/http. I skipped the big frameworks; I'd rather compose small pieces than learn someone's conventions.
Frontend: Next.js
The dashboard is Next.js with React and Tailwind. SSR and static generation handle the marketing pages for SEO, the API routes proxy to the Go backend, and day to day the dev experience is pleasant enough that I don't fight it.
Database: PostgreSQL + Redis
Postgres holds the source of truth. JSONB for flag metadata, partial indexes where they earn their keep, LISTEN/NOTIFY to invalidate caches. It's grown to 29 tables, 67 indexes, 29 foreign keys.
Redis sits in front as the evaluation cache. When an SDK asks whether flag X is on for user Y, Redis answers in milliseconds, and pub/sub keeps it fresh.
Rollgate's Feature Set
There's more under the hood than an on/off switch. The feature set covers most of the release lifecycle, so here's the tour.
Feature Flags with Multiple Types
A flag can return one of four types:
- Boolean, the classic on/off, which is what you want for kill switches and plain toggles.
- String, handy for A/B variants like "variant-a" / "variant-b", or for swapping copy without a deploy.
- Number, for limits, percentages, or a knob on some algorithm.
- JSON, when you need a whole config object: a layout, a plan's feature set, a chunk of business rules.
Gradual Rollouts
Percentage rollout turns a flag on for a slice of users. The trick is consistent hashing (MurmurHash3) over the user ID, so a given user lands the same way every time and I don't have to store who's in and who's out:
hash = MurmurHash3(flagKey + userId) % 10000
isEnabled = hash < rolloutPercentage * 100
That gets you 0.01% precision. Ramp 1, 5, 25, 100 at whatever pace your nerves allow, watching the dashboards between steps.
User Targeting and Segments
Targeting switches a flag on for specific groups based on attributes:
- Match on user attributes: email, plan, country, app version, or anything custom you send.
- Operators include equals, contains, startsWith, endsWith, regex, in, greaterThan, lessThan.
- Define a segment like "Beta Users" or "Enterprise Customers" once and reuse it across flags.
- Combine conditions with AND/OR when one rule isn't enough.
Kill Switches
If I had to keep one feature, it'd be the kill switch. Something breaks in production (a bug, a slow query, a payment provider having a bad day) and you need that feature gone now, not after a deploy.
Every flag has a toggle that propagates over SSE in seconds. No deploy, no SSH, no developer required. A product manager can kill a feature from their phone on the train.
I've watched teams burn 30 to 45 minutes on a traditional rollback: revert the commit, wait for CI, deploy, verify it actually took. A kill switch is two seconds. Mid-incident, the difference is real money.
Scheduled Changes
Scheduled changes let you queue a flag change for a date and time:
- Line up a launch: enable the feature Friday at 2pm, right when marketing publishes the blog post.
- Run a promo on rails: Black Friday banner on from Friday, off Monday.
- Automate the ramp: 1% Monday, 10% Wednesday, 50% Friday, 100% the Monday after.
- Sunset something on schedule: turn the old API off on April 1st and forget about it.
Each one shows up in the flag's timeline as pending, executed, or cancelled. A cron job on the server wakes up every minute and applies whatever's due.
Instant Rollback
Every change to a flag gets saved with a timestamp, who did it, and a full snapshot of the state. Rollback puts the flag back exactly as it was at some earlier point. The value, sure, but also the targeting rules, the percentages, the scheduled changes.
It's more than an undo. You can jump back five changes, or to a specific version from last Tuesday. The audit log keeps the who and the when.
A/B Testing
String variant flags give you native A/B testing. Instead of true/false, the flag hands back a variant ("control", "variant-a", "variant-b") with whatever split you set.
Evaluation tracking records which variant each user saw, with timestamp and context, so you can line variants up against business metrics in whatever analytics tool you already use.
I deliberately left out a built-in stats engine. Teams already have analytics they trust, and I didn't want to be one more data silo to reconcile.
Multi-Environment
Every project gets separate environments: development, staging, production, plus any others you spin up. Flags live independently in each, so a flag can be on in staging and off in prod.
Each environment carries its own API keys (server and client), its own rules, its own history. It's the guardrail against the classic "oh no, that was prod, not staging".
Audit Log
Everything lands in an immutable audit log. Who created, changed, or deleted a flag, the before/after diff, the exact timestamp, the IP and user agent it came from.
Retention follows the plan: 3 days on Free, 14 on Starter, 90 on Pro, a year on Growth. When you're chasing a bug or answering a compliance question, that trail is the thing that saves you.
Webhooks
Webhooks tell other systems when a flag moves. Create, update, delete: each one fires an HTTP POST to your URLs with the full event payload.
People wire them to ping Slack on a prod change, kick off a deploy, nudge monitoring, sync a project tool. There's automatic retry with exponential backoff, and every delivery is logged so you can see what landed.
Analytics and Usage Tracking
Evaluations get tracked as they happen: total count, unique users, the true/false split, how it trends over time. Usage caps go by plan: 500K requests a month on Free, 1M on Starter, 3M on Pro, 12M on Growth.
13 SDKs
There are 13 SDKs, which is probably the part that ate the most evenings:
- JavaScript and TypeScript: sdk-core, sdk-node, sdk-browser, sdk-react, sdk-vue, sdk-angular, sdk-svelte.
- Mobile: sdk-react-native, sdk-flutter.
- Backend: sdk-go, sdk-java, sdk-python, sdk-dotnet.
They all share the same machinery: a local cache, a circuit breaker, retry with backoff, event buffering, live SSE updates. If the API goes dark, the SDK keeps serving the last values it had instead of falling over.
import { RollgateProvider, useFlag } from '@rollgate/sdk-react';
function App() {
return (
<RollgateProvider apiKey="rg_client_your_key">
<Dashboard />
</RollgateProvider>
);
}
function Dashboard() {
const showNewFeature = useFlag('new-dashboard', false);
return showNewFeature ? <NewDashboard /> : <LegacyDashboard />;
}
System Architecture
Client SDK -> API Server (Go) -> Redis Cache -> PostgreSQL
^
SSE Connection (real-time updates)
A flag evaluation lands around 200µs. P99 stays under a millisecond. The system holds tens of thousands of SSE connections open at once.
Technical Challenges
Race Conditions on Updates
Two people editing the same flag at once used to worry me. Optimistic locking with a version counter handles it: if the flag changed under you, the write comes back 409 Conflict and you reconcile.
Circuit Breaker in SDKs
If the API is down, an SDK absolutely cannot take the host app down with it. The circuit breaker runs the usual three states (Closed, Open, Half-Open), with exponential retry and request dedup so a thundering herd doesn't pile on.
SEO for a SaaS App
I split marketing and app across domains: rollgate.io for the SEO pages (Server Components) and app.rollgate.io for the authenticated dashboard.
Resilience and Monitoring
- Backups: a full Postgres dump every night, shipped to a server in a different datacenter.
- Monitoring: Prometheus, Grafana, Alertmanager, with alerts that actually page me.
- Health checks: API, Web, DB, and Redis get poked every 60 seconds.
- Disaster recovery: one script rebuilds the whole stack in about 20 minutes, and I've tested it more than once.
- SDK resilience: local cache plus circuit breaker means clients survive an outage.
The Numbers
| Metric | Value |
|---|---|
| Go files (non-test) | 147 |
| TypeScript/TSX files | 168 |
| API endpoints | 114 |
| DB tables | 29 |
| Total tests | ~850 |
| Published SDKs | 13 |
| Codebase score | 7.86/10 |
What I Learned
1. Simplicity Wins
No Kubernetes. Docker Compose runs the whole thing and I sleep fine. No microservices either; a modular monolith is plenty for one person to reason about. Every bit of complexity bills you in maintenance forever, and most of it isn't worth the rent.
2. Tests Save Lives
Around 850 tests is what lets me refactor without holding my breath. CI runs the lot on every PR and won't let a deploy through if anything's red.
3. Billing Is Harder Than the Product
Payments with Paddle were harder than half the product. Webhooks, subscription lifecycle, proration, dunning, tax. Take the provider that swallows all of it (Paddle, Lemon Squeezy) instead of wiring raw Stripe yourself, unless billing is the thing you want to spend your life on.
4. SEO Requires Constant Attention
If Google doesn't index you, you don't exist. I lost weeks to SSR, JSON-LD, sitemaps, meta tags, and it's never actually finished.
5. Building Solo Is Sustainable (With Limits)
Writing the code turned out to be the comfortable part. Finding people who want it is the hard one. Marketing, support, growth: different muscles, and I'm still building them.
The Market: Competitors and Positioning
A handful of players own the feature flags market, most of them priced for enterprise:
| Platform | Pricing | Strengths | Limitations |
|---|---|---|---|
| LaunchDarkly | From $833/mo | Market leader, enterprise-ready, vast integrations | Prohibitive for startups, excessive complexity |
| Flagsmith | From $45/mo | Free self-hosted, API-first | Less polished UI, fewer SDKs |
| Unleash | From $80/mo | Mature self-hosted, good docs | Complex setup, dated UI |
| PostHog | Usage-based | Complete suite (analytics + flags), open-source | Flags aren't the focus, overhead |
| Rollgate | Free -> €45/mo | Accessible pricing, 13 SDKs, 5-min setup | New to market, growing community |
Choosing the Pricing Model
Pricing was one of the calls I changed my mind on the most. Looking across competitors, it came down to three shapes:
- Per-seat, like LaunchDarkly. Scales with headcount and punishes big teams; $70k a year for 50 developers isn't a weird number.
- Usage-based, like PostHog. Scales with traffic and makes budgeting a guessing game, since a spike can blow up the bill.
- Fixed tiers, which is what I went with. The price is set by features and limits, so a customer knows the number before the month starts.
I picked fixed tiers because for startups and small teams, a predictable invoice beats a clever one. The four plans:
| Plan | Monthly | Annually | Requests/mo | Team | Target |
|---|---|---|---|---|---|
| Free | €0 | €0 | 500K | 3 members | Side projects, MVPs |
| Starter | €45/mo | €39/mo | 1M | 5 members | Early-stage startups |
| Pro | €119/mo | €99/mo | 3M | 15 members | Growing teams |
| Growth | €349/mo | €299/mo | 12M | 50 members | Scale-ups, high traffic |
The free tier is deliberately generous. 500K requests a month covers an app with thousands of active users. I'd rather lower the bar to entry: if someone tries Rollgate and it just works, moving up to Starter when the project grows is the easy, obvious step.
Why Not Open Source?
I looked hard at open-core (free core, paid enterprise bits). It really needs a live community to pull its weight, and for a one-person project the failure mode is handing everything away and monetizing none of it. So it's SaaS with a generous free tier for now, and a self-hosted build under BSL (Business Source License) on the roadmap, the same route HashiCorp and Sentry took.
What's Next
Rollgate is live at rollgate.io, free tier and all (500K requests a month). On the bench: smarter A/B testing that calls the winner on its own, teams with SSO, the self-hosted build, and a full OpenAPI spec.
If any of this is useful, or you're in the middle of your own solo build and want to compare notes, the comments are open.
I'm building Rollgate, a feature flag platform for developers. Questions about flags? Leave them below, I read everything.
Top comments (0)