Skip to content

Full Stack

Feature Flags Done Right: OpenFeature, Unleash, and a Zero-Downtime Rollout Playbook

August 26, 20269 min readRasel Hossain
Feature Flags Done Right: OpenFeature, Unleash, and a Zero-Downtime Rollout Playbook

Quick answer

Feature flags let you decouple deploy from release. This guide shows how to set up OpenFeature with a self-hosted Unleash backend, run zero-downtime progressive rollouts using percentage-based strategies with stickiness, and build kill switches that let you disable risky features in seconds without redeploying.

Feature Flags Done Right: OpenFeature, Unleash, and a Zero-Downtime Rollout Playbook

Shipping code to production on Friday afternoon used to be a rite of passage. Today, it's a résumé-generating event. Over the last six years building production systems for clients across fintech, SaaS, and e-commerce, I've learned one truth above all: the deploy button is not the release button. Feature flags are the lever that separates those two, and when you wire them up correctly, you get safer rollouts, instant kill switches, and the ability to test in production without the panic.

In this guide, I'll walk you through a battle-tested feature flag setup using OpenFeature (the vendor-neutral standard) and Unleash (a powerful self-hosted flag service). We'll also cover the rollout playbook I use on real projects — canary releases, percentage-based progressive delivery, and kill switches that actually save you at 2 AM.

feature flags done - Image 2

Why Feature Flags Are No Longer Optional

If you're still doing all-or-nothing deployments, you're not alone — but you're leaving reliability on the table. Feature flags give you:

feature flags done - Image 3

  • Decoupling deploy from release: Code ships dark, gets activated later.
  • Targeted rollouts: Ship to internal users, then 1%, then 10%, then everyone.
  • Instant rollback: Flip a flag instead of redeploying.
  • A/B testing infrastructure: Compare variants without forking the codebase.
  • Kill switches: Wrap risky features behind a flag you can disable in seconds.

I've personally used flags to ship a payment provider migration to 5% of traffic, watch error rates spike in real time, and roll back before a single customer support ticket came in. That single feature paid for every hour I spent setting up the system.

Meet OpenFeature: The Vendor-Neutral Standard

Before we touch code, let's talk about OpenFeature. It's a CNCF incubating project that gives you a single, provider-agnostic API for feature flags. The killer benefit: you can swap flag providers (Unleash, LaunchDarkly, Flagsmith, your own DB) without rewriting application code.

Here's what the API looks like in Node.js:

import { OpenFeature } from '@openfeature/server-sdk';
import { UnleashProvider } from '@openfeature/unleash-provider';

// Register Unleash as the provider
const provider = new UnleashProvider({
  url: 'http://localhost:4242/api/',
  appName: 'checkout-service',
  apiKey: process.env.UNLEASH_API_KEY,
});

await OpenFeature.registerProvider(provider);
const client = OpenFeature.getClient();

// Evaluate a flag
const showNewCheckout = client.getBooleanValue('new-checkout', false);

if (showNewCheckout) {
  // new code path
} else {
  // legacy code path
}

Notice the default value (false in this case). This is critical: if your flag service goes down or is unreachable, the default keeps your app running safely. Never let a flag service outage take down production.

Self-Hosting Unleash with Docker

Unleash has a generous open-source tier that's more than enough for most teams. I self-host it on a small VPS using Docker Compose — here's the minimal setup:

# docker-compose.yml
version: "3.9"

services:
  unleash:
    image: unleashorg/unleash-server:latest
    ports:
      - "4242:4242"
    environment:
      DATABASE_URL: "postgres://unleash:unleash@db/unleash"
      UNLEASH_URL: "http://localhost:4242"
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      POSTGRES_USER: unleash
      POSTGRES_PASSWORD: unleash
      POSTGRES_DB: unleash
    volumes:
      - unleash-db:/var/lib/postgresql/data

volumes:
  unleash-db:

Run docker compose up -d, then head to http://localhost:4242. Create a project, generate an API token, and you're ready to define flags. For production, I'd recommend putting this behind HTTPS with Caddy or Nginx, and backing up the Postgres volume nightly — the flag state is part of your production state.

Defining Flags That Won't Bite You Later

A flag without a removal plan is technical debt. Every flag I create follows three rules:

  1. Name it after the behavior, not the implementation. new-checkout is better than use-stripe-element-v2.
  2. Add an owner and a removal date in the description. I tag flags #cleanup-2024-Q4 so they show up in audits.
  3. Default to the safest value. New flags default to false (or the legacy path) until you're ready.

In Unleash, you can configure activation strategies directly in the UI:

  • Default: on/off for everyone
  • UserID: targeted list of user IDs
  • Gradual rollout: percentage-based with stickiness
  • Constraints: country, email domain, custom properties

The gradual rollout strategy is the one you'll use most. It supports sticky bucketing so a user always lands in the same variant — critical for consistent experiences.

The Zero-Downtime Rollout Playbook

Here's the exact sequence I follow on every non-trivial feature. It's saved me from at least three production incidents in the past year.

Step 1: Ship Dark, Toggle Off

Merge the feature behind a flag, deploy to production, leave the flag off for everyone. The code path is exercised in staging via test flags, but in production it's dormant. Run your smoke tests, your load tests, your chaos tests — all of them see the flag as off.

Step 2: Internal Canary

Enable the flag for users with the email domain @yourcompany.com or a custom internal property. This catches obvious bugs without any customer risk. I usually leave this on for 24–48 hours while the team actually uses the app.

In Unleash, add a constraint:

email endsWith @yourcompany.com

Step 3: Progressive Percentage Rollout

Now the fun part. Ramp to 1% for one hour, monitoring error rates, latency, and conversion. If the metrics look healthy, push to 5%, then 25%, then 50%, then 100%. I automate this with a simple script that reads metrics and updates the flag, but a manual runbook works fine too.

The key insight: each step should be long enough to gather statistically meaningful data. For high-traffic apps, an hour is enough. For low-traffic apps, you may need a day at each stage.

Step 4: Bake Time, Then Cleanup

Once you're at 100%, the flag stays in for one to two weeks. Why? Because some bugs only show up over time — memory leaks, edge cases in long-lived sessions, data drift. After the bake period, remove the flag and the legacy code path in a single PR.

Building a Kill Switch You'll Actually Use

A kill switch is a flag that exists solely to turn a feature off fast. It should be:

  • Always evaluated — no caching, no offline mode override
  • In a separate "emergency" project with restricted write access
  • Documented in the runbook with exact flag names and toggle steps

I wrap risky integrations (new payment providers, ML model calls, third-party APIs) behind a kill switch flag from day one. Here's a pattern I like:

async function processPayment(order: Order) {
  const useNewProvider = client.getBooleanValue('use-new-payment-provider', false);
  
  try {
    if (useNewProvider) {
      return await newPaymentProvider.charge(order);
    }
    return await legacyPaymentProvider.charge(order);
  } catch (err) {
    // Telemetry: which path failed?
    metrics.increment('payment.failure', { variant: useNewProvider ? 'new' : 'legacy' });
    throw err;
  }
}

If the new provider starts failing, you flip one toggle in Unleash, and every subsequent request falls back to the legacy provider. No deploy, no rollback commit, no all-hands meeting.

OpenFeature in a Polyglot Stack

One of the underrated wins of OpenFeature is the consistent API across languages. Your Node.js backend, Go service, Python worker, and React frontend all evaluate flags the same way:

// Go
client := openfeature.NewClient()
showNewCheckout, _ := client.BooleanValue("new-checkout", false, nil)
# Python
from openfeature import api
client = api.get_client()
show_new_checkout = client.get_boolean_value("new-checkout", False)
// React frontend
import { useFlag } from '@openfeature/react-sdk';

function CheckoutButton() {
  const showNewCheckout = useFlag('new-checkout', false);
  return showNewCheckout ? <NewCheckout /> : <LegacyCheckout />;
}

This means your rollout strategy stays consistent even as your stack evolves. I can't tell you how many times this has mattered on client projects where the team added a new service in a different language mid-project.

Common Pitfalls and How to Avoid Them

After setting this up for a dozen teams, here are the mistakes I see over and over:

  • Flag sprawl: Hundreds of stale flags polluting the UI. Fix: scheduled cleanup sprints, ownership tags, and a "last evaluated" metric you can sort by.
  • Caching too aggressively: A flag change should propagate in seconds, not minutes. Configure short TTLs on the SDK cache.
  • Forgetting defaults: If your flag service is down, the app should still run — on the default. Test this by unplugging Unleash.
  • Using flags for config: Feature flags and config are different problems. Don't store API keys in your flag service.
  • No metrics per variant: You can't optimize what you can't measure. Always tag your telemetry with the active variant.

Wrapping Up

Feature flags aren't a luxury anymore — they're table stakes for any team that ships faster than once a month. The combination of OpenFeature for vendor neutrality and Unleash for a self-hosted, open-source backend gives you everything you need without monthly SaaS bills or lock-in. Add a disciplined rollout playbook — dark deploy, internal canary, progressive percentage, bake time, cleanup — and you have a release process that scales from a side project to a hundred-engineer org.

If you're building anything that customers depend on, set this up before you need it. The first time a kill switch saves you from a 2 AM incident, you'll know it was worth the afternoon it took to wire up.

Got questions about a specific rollout scenario or want me to dive deeper into OpenFeature's evaluation context? Drop a comment below or reach out — I help teams design release systems that actually work under pressure.


FAQ: Feature Flags, OpenFeature, and Unleash

What is the difference between OpenFeature and Unleash?

OpenFeature is a vendor-neutral specification and SDK standard for evaluating feature flags — think of it as an API contract. Unleash is one of many flag management backends (providers) that OpenFeature-compatible SDKs can talk to. You use OpenFeature in your application code and plug in Unleash as the provider; you can swap Unleash for LaunchDarkly, Flagsmith, or a custom backend later without changing application code.

Is Unleash free for production use?

Yes. Unleash has a fully open-source core (Apache 2.0) that includes unlimited flags, projects, and environments. Their commercial offering adds enterprise features like SSO, audit logs, and support, but the self-hosted open-source version is production-grade and used by companies at serious scale.

How do I prevent feature flag technical debt?

Three practices work: (1) tag every flag with an owner and a removal date, (2) run a monthly "flag cleanup" sprint where you delete anything older than 30 days post-100% rollout, and (3) instrument your app to log "last evaluated at" timestamps so you can find flags nobody's reading anymore.

Can I use OpenFeature with multiple providers at once?

Yes, OpenFeature supports a layered approach. You can stack providers — for example, a local in-memory provider for tests, with Unleash as the fallback in production. You can also have per-domain providers so one team uses Unleash and another uses LaunchDarkly within the same application.

What's the safest default value for a new feature flag?

The default value is what your app uses when the flag service is unreachable or the flag doesn't exist. Always default to the safer, more conservative behavior — usually the existing legacy code path. That way, an outage in your flag infrastructure cannot accidentally enable a half-built feature.

How long should a percentage rollout stage last?

It depends on your traffic volume. High-traffic apps (millions of requests per day) can move through 1% → 5% → 25% → 100% in a few hours. Low-traffic apps (thousands of requests per day) should hold each stage for at least 24 hours to gather meaningful error rate and latency data. The goal is enough samples to detect a 2x regression with statistical confidence.

#DevOps#Feature Flags#OpenFeature#Unleash#Release Management

How to do it

  1. 1

    Set up Unleash with Docker Compose

    Create a docker-compose.yml that runs the unleashorg/unleash-server image alongside a Postgres database. Expose port 4242, set the DATABASE_URL environment variable, and start the stack with docker compose up -d. Then create an admin user, generate an API token for your application, and create your first project.

  2. 2

    Integrate OpenFeature SDK with Unleash provider

    Install @openfeature/server-sdk and the @openfeature/unleash-provider package in your application. Register Unleash as the provider using your API key and URL, then create a client. Replace hardcoded feature checks with client.getBooleanValue calls that include a safe default value. This gives you a vendor-neutral API you can later swap to another provider.

  3. 3

    Ship dark, then enable for internal users

    Deploy your code with the new feature behind a flag set to false. Verify the legacy path is still working in production. Then in Unleash, add a constraint on the email domain (or a custom userId list) so only your team sees the new feature. Leave this on for 24 to 48 hours while your team exercises the code path.

  4. 4

    Run a progressive percentage rollout

    Enable the gradual rollout strategy in Unleash starting at 1% with stickiness enabled. Monitor error rates, latency, and business metrics at each stage. If the metrics stay healthy for the agreed window, increase to 5%, then 25%, then 50%, then 100%. Each step should be long enough to give you statistical confidence — usually one hour for high-traffic apps, 24 hours for low-traffic apps.

  5. 5

    Add kill switches and clean up

    Wrap any risky third-party integrations behind their own dedicated flag that defaults to false. Document the flag name in your incident runbook so anyone on call can flip it off. After the feature has been at 100% for one to two weeks, remove the flag and the legacy code path in a single cleanup PR. Tag the cleanup ticket with the original feature ticket for traceability.

Frequently asked questions

What is the difference between OpenFeature and Unleash?

OpenFeature is a vendor-neutral specification and SDK standard for evaluating feature flags — think of it as an API contract. Unleash is one of many flag management backends (providers) that OpenFeature-compatible SDKs can talk to. You use OpenFeature in your application code and plug in Unleash as the provider; you can swap Unleash for LaunchDarkly, Flagsmith, or a custom backend later without changing application code.

Is Unleash free for production use?

Yes. Unleash has a fully open-source core (Apache 2.0) that includes unlimited flags, projects, and environments. Their commercial offering adds enterprise features like SSO, audit logs, and support, but the self-hosted open-source version is production-grade and used by companies at serious scale.

How do I prevent feature flag technical debt?

Three practices work: (1) tag every flag with an owner and a removal date, (2) run a monthly flag cleanup sprint where you delete anything older than 30 days post-100% rollout, and (3) instrument your app to log last-evaluated-at timestamps so you can find flags nobody is reading anymore.

What is the safest default value for a new feature flag?

The default value is what your app uses when the flag service is unreachable or the flag does not exist. Always default to the safer, more conservative behavior — usually the existing legacy code path. That way, an outage in your flag infrastructure cannot accidentally enable a half-built feature.

How long should each stage of a percentage rollout last?

It depends on your traffic volume. High-traffic apps (millions of requests per day) can move through 1% to 5% to 25% to 100% in a few hours. Low-traffic apps (thousands of requests per day) should hold each stage for at least 24 hours to gather meaningful error rate and latency data. The goal is enough samples to detect a 2x regression with statistical confidence.

More articles

Related reading from the same areas — practical notes on shipping software.

View all articles

Liked the article?

Have a similar problem in your business? Let's talk about building the fix.

Start a project