Self-hosted feature flags without a server to run
Self-hosted feature flags usually mean standing up a server: an API process, a database behind it, and the patching and uptime work that come with running both. dif skips the server. Every flag is a Markdown file in your repo, and the SDK reads it locally with no network call, so self-hosting dif is just checking the file into git.
Open-source tools solved the vendor lock-in half of self-hosting, not the ops half. Unleash runs a server backed by a Postgres database. GrowthBook packages a NextJS front end, an Express API, and a Python stats engine on top of MongoDB. Flagsmith needs a backend API and a database of its own. You own the code in each case, but you still operate a service, and a service goes down.
This post covers what self-hosting a feature-flag tool usually requires, how dif avoids running a service for flagging itself, the one place you might still want a hosted service, and how to get started.
Key Takeaways
- Self-hosting usually means a server: Unleash runs on Postgres, and GrowthBook runs a NextJS app, an Express API, and a Python stats engine on MongoDB.
- dif has nothing to self-host for flagging. Every flag is a Markdown file in the repo, and the ~5 kB SDK evaluates it locally with no network call.
- Install is
npm install -g @dif.sh/cli, thendif init. No account, no API key, nodocker-compose.yml.dif validateruns in CI and catches orphaned flag calls and colliding experiments, the job a running server would otherwise do.- The tradeoff: no server also means no live push. Changing a flag is a commit and a deploy, not an instant API call.
What self-hosting usually requires
“Self-hosted” in most feature-flag tools means you run the vendor’s server yourself instead of them running it for you. The database, the uptime, and the backups move onto your team, but the architecture does not change: a service sits between your application and the decision of which variant a user gets.
That service is real infrastructure. Unleash needs Postgres 15 or later behind its API. GrowthBook’s Docker image runs three processes (the front end, the API, and the stats engine) and needs MongoDB for login credentials, cached results, and metadata. Flagsmith’s deployment guide walks through its own backend API and database.
None of this is a knock on those projects. A hosted stats engine and a login system are real features that need a server to run. But if what you actually want is a flag that turns a code path on or off, a database and an API process is a lot of surface area for that one job.
Feature flags self-hosted with no server to run
dif has nothing to self-host, because there is nothing that runs as a service. A flag is a single Markdown file, with frontmatter on top and prose underneath:
---
id: new-checkout
status: active
owner: sam@acme.com
surface: checkout
variants:
- id: "off"
weight: 90
- id: "on"
weight: 10
---
## Brief
Ramp to 25% once guardrails hold for a week.
That file lives in dif/flags/active/new-checkout.md, next to the code it gates. Install the CLI and scaffold a project:
npm install -g @dif.sh/cli
dif init
dif init writes a dif/ directory and a typed client straight into your repo. There is no account, no API key, no docker-compose.yml to maintain, and nothing listening on a port. The .md format covers the full frontmatter schema, and the feature flags page covers the model end to end.
How a flag resolves without a service to call
A hosted tool resolves a flag by calling its server. dif resolves a flag by hashing the user id, locally, in the SDK:
if (dif("new-checkout") === "on") {
return renderNewCheckout();
}
Under the hood, the variant is the first four bytes of SHA-256(salt || user_id), read as an integer and taken mod 10,000, picked from the flag’s cumulative weights. Nothing is stored and nothing is fetched. The SDK, @dif.sh/sdk, is about 5 kB gzipped with zero dependencies, and it runs the same way in the browser and on the server. Because the computation is a pure function of the user id and the flag’s salt, a user gets the same variant on every request, and no database has to store the assignment.
CI does the job a running server would do
A running server earns its keep by catching mistakes before they ship: a bad weight, a flag nobody references anymore, two experiments fighting over the same users. Without a server, dif validate does that job in CI instead:
$ dif validate
✓ 12 flags checked
✗ new-checkout: variant weights sum to 90, expected 100
✗ orphan: "legacy-nav" referenced in no source file
It confirms weights sum to 100, checks that the owner is a real email, scans your app source for dif("...") call sites to flag orphans, and catches two active experiments colliding on one surface before either ships. A broken flag fails the pull request like a broken build, which is the same guarantee a server-backed tool gives you, just enforced at review time instead of at request time.
The cost of having no server
No server also means no live push. Turning a flag off is editing one number to 0 and merging, not an instant call to an API. If your build and deploy take ten minutes, your worst-case time to kill a flag is ten minutes. A hosted server can flip a flag in under a second, and for an ops kill switch on a payment path, that second is worth running a service for.
The other place a server earns its place is analytics. dif’s CLI and SDK run the flag itself with nothing to host, but if you want hosted statistical analysis instead of forwarding events yourself, dif Cloud is an optional service at cloud.dif.sh, $50/mo flat for 1M events with unlimited seats. It is the one part of dif you might choose to run as a service, and it is opt-in, not required to flag anything.
Getting started with self-hosted dif
There is no server to provision, so getting started is the install and nothing else:
npm install -g @dif.sh/cli
dif init
From there, dif new drafts a flag file pre-loaded with the surface’s last three learnings, dif validate checks it, and dif build compiles the active files into a typed client. See the CLI reference for all six verbs, and feature flags in git for how the file-based model fits a pull-request workflow.
FAQ
What does “self-hosted” mean for feature flags? It means you run the tool yourself instead of a vendor running it for you. For most tools that still means operating a server and a database; for dif it means nothing more than having the repo, since there is no service to run.
Do I need a database to self-host feature flags? With Unleash or GrowthBook, yes: Postgres for Unleash, MongoDB for GrowthBook. With dif, no. The flag is a file, and assignment is computed locally from a hash, so there is nothing to persist.
Can I self-host feature flags without running a server? Yes, with dif. The CLI and SDK run entirely from your repo and your app’s own process. The only optional service is dif Cloud, and that is for hosted analytics, not for evaluating flags.
Is dif free to self-host? The CLI and SDK are MIT licensed and free, with no account or API key required. dif Cloud, the hosted analytics layer, is a separate $50/mo paid option you can skip entirely.
How does this compare to a self-hosted LaunchDarkly alternative like Unleash or Flagsmith? Unleash and Flagsmith are open source and self-hostable, but both run as a backend service with its own database. dif has no backend for flagging itself: the flag is a file, and the SDK evaluates it in your application’s own process.
A self-hosted feature-flag tool does not have to mean a server you operate. With dif, the flag is a file in your repo, the SDK evaluates it locally, and dif validate catches mistakes in CI instead of at request time against a running service. The cost: no instant push. A flag change is a commit and a deploy, not a toggle. For most flags that tradeoff is fine, and for the ones that need a sub-second kill switch, name that before you pick a tool. Install with npm install -g @dif.sh/cli, run dif init, and the first flag is working in about two minutes with nothing else to provision.