Building a Self-Hosted Feature Flag and A/B Testing Platform with OpenFeature and Flagd
Quick Summary (TL;DR) Feature flag SaaS platforms are convenient but expensive and lock you into proprietary SDKs. This guide shows you how to build a fully self-hosted alternative using OpenFeature (a vendor-neutral standard API) and Flagd (a lightweight, open-source flag engine). You’ll deploy Flagd via Docker or Kubernetes, define flags in JSON, integrate the OpenFeature SDK in Node.js and Python, implement A/B tests, and roll out features progressively — all without a SaaS bill.
Feature flags have become the backbone of modern software delivery. They let you decouple deployment from release, run experiments, and kill problematic features with a single toggle. But most teams reach for a SaaS vendor before asking a simple question: do we need to?
If you’re comfortable running a container, the answer is often no. OpenFeature and Flagd give you a production-grade, vendor-neutral feature flag platform you fully control.
Why Self-Host Your Feature Flag Platform?
SaaS feature flag tools are excellent, but they come with trade-offs that bite as you scale:
- Cost scales with seats and evaluations. A 50-developer team can easily spend five figures annually.
- Data residency and compliance. Sending user identifiers to a third party is a non-starter for regulated industries.
- Vendor lock-in. Proprietary SDKs mean rewriting integration code if you ever switch providers.
- Latency and availability. Your flag evaluation now depends on someone else’s uptime.
Self-hosting flips these constraints. You keep user data in-house, pay only for infrastructure, and — thanks to OpenFeature — retain the freedom to swap engines later.
Understanding OpenFeature and Flagd
OpenFeature: The Standard API
OpenFeature is a CNCF incubating project that defines a vendor-neutral specification for feature flag evaluation. Think of it as the “OpenTelemetry of feature flags.” Your application code talks to the OpenFeature SDK, and you plug in a provider — which could be Flagd, LaunchDarkly, or anything else.
This abstraction is the whole point: your business logic never depends on a specific vendor.
Flagd: The Feature Flag Engine
Flagd is a lightweight, open-source flag evaluation engine, also part of the OpenFeature ecosystem. It reads flag definitions from a JSON file or a remote source, evaluates them, and serves results over gRPC or HTTP. It ships with a native OpenFeature provider, so integration is a two-line change.
Architecture Overview
flowchart TD
A[Git Repo / S3<br/>flags.json] -->|sync| B[Flagd Service]
B -->|gRPC streaming| C[App + OpenFeature SDK]
B -->|gRPC streaming| D[App + OpenFeature SDK]
C --> E[Variant: control]
D --> F[Variant: treatment]
E --> G[Analytics / Metrics]
F --> G
Flagd acts as a central evaluation service. Applications run the OpenFeature SDK with the Flagd provider, which opens a streaming connection. When flags.json changes, Flagd pushes updates instantly — no app restarts.
Prerequisites
- Docker and Docker Compose (local) or a Kubernetes cluster (production)
- Node.js 18+ and/or Python 3.9+
- Basic familiarity with JSON and YAML
- Optional: a Git repository or S3 bucket for GitOps flag storage
Step 1: Deploy Flagd
Option A: Docker Compose (Recommended for Local Development)
Create a docker-compose.yml:
services:
flagd:
image: ghcr.io/open-feature/flagd:latest
command: start --uri file:./etc/flags.json
ports:
- "8013:8013" # gRPC
- "8016:8016" # HTTP/management
volumes:
- ./flags:/etc
Then start it:
docker compose up -d
Flagd now watches ./flags/flags.json and reloads it on every save.
Option B: Kubernetes (Production)
Deploy Flagd as a central service using a Deployment and Service:
apiVersion: apps/v1
kind: Deployment
metadata:
name: flagd
spec:
replicas: 2
selector:
matchLabels: { app: flagd }
template:
metadata:
labels: { app: flagd }
spec:
containers:
- name: flagd
image: ghcr.io/open-feature/flagd:latest
args: ["start", "--uri", "file:/etc/flagd/flags.json"]
ports:
- containerPort: 8013
volumeMounts:
- name: flags
mountPath: /etc/flagd
volumes:
- name: flags
configMap: { name: flagd-config }
For high availability, run multiple replicas behind a Service. Clients connect via gRPC streaming and automatically reconnect if a pod restarts.
Step 2: Define Feature Flags
Flag Definition Schema
Flagd uses a JSON schema where each flag has a state, variants, and a defaultVariant. Targeting rules use a JSONLogic-like syntax.
Example: A/B Test Flag
{
"flags": {
"checkout-redesign": {
"state": "ENABLED",
"variants": {
"control": false,
"treatment": true
},
"defaultVariant": "control",
"targeting": {
"if": [
{ "fractional": [
{ "var": "targetingKey" },
["control", 50],
["treatment", 50]
] }
]
}
}
}
}
The fractional operator hashes the targetingKey (typically a user ID) and assigns a variant based on the weighted buckets — a deterministic, sticky 50/50 split.
Step 3: Integrate OpenFeature SDK
Node.js Example
npm install @openfeature/server-sdk @openfeature/flagd-provider
import { OpenFeature } from '@openfeature/server-sdk';
import { FlagdProvider } from '@openfeature/flagd-provider';
await OpenFeature.setProviderAndWait(
new FlagdProvider({ host: 'localhost', port: 8013 })
);
const client = OpenFeature.getClient();
const context = { targetingKey: 'user-42' };
const showRedesign = await client.getBooleanValue(
'checkout-redesign',
false,
context
);
Python Example
pip install openfeature-sdk openfeature-provider-flagd
from openfeature import api
from openfeature.contrib.provider.flagd import FlagdProvider
api.set_provider(FlagdProvider(host="localhost", port=8013))
client = api.get_client()
show_redesign = client.get_boolean_value(
"checkout-redesign", False, {"targetingKey": "user-42"}
)
Notice how nearly identical the two are — that’s the OpenFeature standard at work.
Step 4: Implement A/B Testing Logic
A flag alone isn’t an experiment. You need three things: assignment, exposure tracking, and outcome measurement.
const variant = await client.getStringValue('checkout-redesign', 'control', context);
// Track exposure
analytics.track('experiment_exposure', {
experiment: 'checkout-redesign',
variant,
userId: context.targetingKey,
});
// Render based on variant
renderCheckout(variant === 'treatment' ? newCheckout : legacyCheckout);
Tracking and Analytics
Send exposure and conversion events to your analytics stack (PostHog, ClickHouse, or even a simple events table). Because assignment is deterministic via fractional, the same user always sees the same variant — critical for valid experiments.
Step 5: Progressive Delivery with Flagd
Canary Releases
Roll out to a small percentage first, then expand:
"targeting": {
"if": [
{ "fractional": [
{ "var": "targetingKey" },
["off", 95],
["on", 5]
] }
]
}
Watch error rates, then bump to 25%, 50%, 100% by editing the JSON. Flagd pushes the change live.
Ring Deployments
Use user attributes to create deployment rings — internal users first, then beta, then everyone:
"targeting": {
"if": [
{ "in": [ { "var": "ring" }, ["internal", "beta"] ] },
"on",
"off"
]
}
Step 6: Managing Flag Configurations
GitOps Workflow
Store flags.json in Git. Every change goes through a pull request, giving you review, history, and rollback for free. Point Flagd at the repo:
flagd start --uri https://raw.githubusercontent.com/org/flags/main/flags.json
Dynamic Updates
Flagd watches its source and reloads automatically. Connected SDKs receive updates over gRPC streaming within milliseconds — no redeploys, no cache invalidation headaches.
Comparison: Self-Hosted vs. SaaS Feature Flags
| Criterion | Self-Hosted (OpenFeature + Flagd) | SaaS (e.g., LaunchDarkly) |
|---|---|---|
| Cost | Infrastructure only | Per-seat + evaluation tiers |
| Data residency | Full control | Vendor-dependent |
| Setup effort | Moderate (DevOps) | Minimal |
| Vendor lock-in | None (standard API) | High (proprietary SDK) |
| Scalability | You manage it | Managed for you |
| Advanced analytics | Build your own | Built-in |
| Uptime SLA | Your responsibility | Vendor-backed |
| Best for | Cost-sensitive, compliance-bound, polyglot teams | Fast-moving teams wanting zero ops |
Best Practices and Pitfalls
- Always pass a
targetingKey. Without it, fractional assignment is non-deterministic and your A/B test is invalid. - Provide sensible defaults. The second argument to
getBooleanValueis what runs if Flagd is unreachable — never let a flag outage break your app. - Version your flag schema. Treat
flags.jsonlike code: review it, test it, tag releases. - Retire flags aggressively. A flag left in code for a year is technical debt. Delete it once the rollout is complete.
- Monitor Flagd itself. Track gRPC connection health and evaluation latency.
- Secure the endpoint. Enable TLS and restrict network access; flag data can leak internal plans.
Conclusion
Self-hosting feature flags no longer means building an engine from scratch. OpenFeature gives you a portable, standard API, and Flagd gives you a lightweight, production-ready engine you can run anywhere. Together they deliver progressive delivery and A/B testing at a fraction of SaaS cost — with your data staying exactly where it belongs.
Start with Docker Compose locally, move to Kubernetes when you’re ready, and let GitOps manage the rest. Your checkout page — and your CFO — will thank you.
FAQ
What is the difference between OpenFeature and Flagd? OpenFeature is a vendor-neutral specification and SDK providing a standard API for flag evaluation. Flagd is a specific engine that implements the OpenFeature provider interface. You use OpenFeature SDKs in your code and configure them with a Flagd provider.
Can I use Flagd without OpenFeature? Yes — Flagd exposes gRPC and HTTP APIs you can call directly. But using OpenFeature gives you a consistent API across providers, making future swaps or combinations trivial.
How does Flagd handle flag updates in real-time? Flagd supports dynamic updates via file watching or a sync service polling a remote source (Git, S3). On change, it reloads and notifies connected clients via gRPC streaming — no app restart required.
Is Flagd suitable for large-scale production use? Yes. It’s lightweight and horizontally scalable. Run multiple instances behind a load balancer, and it handles caching and efficient evaluation for production workloads.
How do I perform A/B testing with OpenFeature and Flagd?
Define a flag with variants and use fractional targeting on a consistent user identifier. Evaluate the flag in your app, then track exposures and outcomes in your analytics system to measure impact.
What are the licensing and cost implications of self-hosting? Both projects are Apache 2.0 licensed — no fees. Your costs are infrastructure and operational effort, often far cheaper than SaaS at scale.
Can I use Flagd with multiple programming languages? Yes. OpenFeature offers SDKs for Java, JavaScript/TypeScript, Python, Go, .NET, Ruby, and PHP, with Flagd providers for each — perfect for polyglot environments.