Feature Flags with OpenFeature and Flagsmith: Ship Safely in Production

Implement feature flags using the OpenFeature standard and Flagsmith. Learn progressive rollouts, A/B testing, kill switches, and trunk-based development...

Y
Yash Pritwani
13 min read

Why Feature Flags Change Everything

Feature flags decouple deployment from release. You can deploy code to production without making it visible to users, then gradually roll it out, test it with specific segments, or kill it instantly if something goes wrong.

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 180" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="180" rx="12" fill="#1a1a2e"/><rect x="30" y="55" width="90" height="50" rx="8" fill="#6366f1" opacity="0.9"/><text x="75" y="85" text-anchor="middle" fill="#ffffff" font-size="12" font-family="system-ui">Code</text><rect x="150" y="55" width="90" height="50" rx="8" fill="#3b82f6" opacity="0.9"/><text x="195" y="85" text-anchor="middle" fill="#ffffff" font-size="12" font-family="system-ui">Build</text><rect x="270" y="55" width="90" height="50" rx="8" fill="#a855f7" opacity="0.9"/><text x="315" y="85" text-anchor="middle" fill="#ffffff" font-size="12" font-family="system-ui">Test</text><rect x="390" y="55" width="90" height="50" rx="8" fill="#2dd4bf" opacity="0.9"/><text x="435" y="85" text-anchor="middle" fill="#1a1a2e" font-size="12" font-family="system-ui">Deploy</text><rect x="510" y="55" width="60" height="50" rx="8" fill="#f59e0b" opacity="0.9"/><text x="540" y="85" text-anchor="middle" fill="#1a1a2e" font-size="12" font-family="system-ui">Live</text><path d="M122,80 L148,80" stroke="#e2e8f0" stroke-width="2" marker-end="url(#arrow1)"/><path d="M242,80 L268,80" stroke="#e2e8f0" stroke-width="2" marker-end="url(#arrow1)"/><path d="M362,80 L388,80" stroke="#e2e8f0" stroke-width="2" marker-end="url(#arrow1)"/><path d="M482,80 L508,80" stroke="#e2e8f0" stroke-width="2" marker-end="url(#arrow1)"/><defs><marker id="arrow1" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><text x="300" y="145" text-anchor="middle" fill="#94a3b8" font-size="11" font-family="system-ui">Continuous Integration / Continuous Deployment Pipeline</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">A typical CI/CD pipeline: code flows through build, test, and deploy stages automatically.</p></div>

This enables:

Trunk-based development: Everyone merges to main. No long-lived feature branches.
Progressive rollouts: Show features to 1% of users, then 10%, then 50%, then 100%.
Instant rollback: Toggle a flag instead of deploying a rollback.
A/B testing: Show different variants to different user segments.

OpenFeature: The Standard

OpenFeature is a CNCF project that provides a vendor-neutral API for feature flags. Instead of coupling your code to a specific feature flag provider, you code against the OpenFeature SDK and swap providers without changing application code.

// Install OpenFeature SDK and Flagsmith provider
// npm install @openfeature/server-sdk @openfeature/flagsmith-provider

import { OpenFeature } from '@openfeature/server-sdk';
import { FlagsmithProvider } from '@openfeature/flagsmith-provider';

// Initialize with Flagsmith provider
const provider = new FlagsmithProvider({
  environmentKey: process.env.FLAGSMITH_KEY,
});
await OpenFeature.setProviderAndWait(provider);

// Get a client
const client = OpenFeature.getClient();

// Evaluate a boolean flag
const showNewCheckout = await client.getBooleanValue(
  'new-checkout-flow',
  false,  // default value
  { targetingKey: userId }
);

if (showNewCheckout) {
  renderNewCheckout();
} else {
  renderLegacyCheckout();
}

The beauty of OpenFeature: if you later switch from Flagsmith to LaunchDarkly or PostHog, you only change the provider initialization. All flag evaluations remain the same.

Flagsmith: Self-Hosted Feature Flags

Flagsmith is our recommended feature flag platform because it is fully open-source and self-hostable. At TechSaaS, we run it as a Docker container alongside our other services.

# docker-compose.yml
services:
  flagsmith:
    image: flagsmith/flagsmith:latest
    container_name: flagsmith
    restart: unless-stopped
    environment:
      DATABASE_URL: postgresql://postgres:secret@postgres:5432/flagsmith
      DJANGO_ALLOWED_HOSTS: "*"
      ALLOW_ADMIN_INITIATION_VIA_CLI: "true"
      FLAGSMITH_DOMAIN: flags.techsaas.cloud
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.flagsmith.rule=Host(`flags.techsaas.cloud`)"
      - "traefik.http.services.flagsmith.loadbalancer.server.port=8000"
    networks:
      - app-net
    mem_limit: 512m

Feature Flag Patterns

1. Release Flag (Boolean)

The simplest pattern. Show or hide a feature:

# Python example
from openfeature import api

client = api.get_client()

if client.get_boolean_value("dark-mode", False):
    enable_dark_mode()

2. Progressive Rollout (Percentage)

Roll out gradually to catch issues early:

Day 1: 1% of users (internal team)
Day 3: 10% of users (power users)
Day 7: 50% of users
Day 14: 100% of users

In Flagsmith, this is configured in the UI with percentage-based rules. The SDK handles consistent hashing so the same user always sees the same variant.

3. User Segment Targeting

Show features to specific user segments:

const context = {
  targetingKey: userId,
  plan: 'enterprise',
  country: 'US',
  betaTester: true,
};

const value = await client.getBooleanValue('advanced-analytics', false, context);

Flagsmith evaluates rules like "if plan is enterprise AND country is US, enable the feature."

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 220" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="220" rx="12" fill="#1a1a2e"/><rect x="200" y="15" width="200" height="40" rx="8" fill="#6366f1"/><text x="300" y="40" text-anchor="middle" fill="#ffffff" font-size="13" font-family="system-ui" font-weight="bold">Orchestrator</text><line x1="250" y1="55" x2="100" y2="90" stroke="#e2e8f0" stroke-width="1.5" stroke-dasharray="4,3"/><line x1="300" y1="55" x2="300" y2="90" stroke="#e2e8f0" stroke-width="1.5" stroke-dasharray="4,3"/><line x1="350" y1="55" x2="500" y2="90" stroke="#e2e8f0" stroke-width="1.5" stroke-dasharray="4,3"/><rect x="40" y="90" width="120" height="110" rx="8" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="100" y="110" text-anchor="middle" fill="#3b82f6" font-size="11" font-family="system-ui">Node 1</text><rect x="55" y="120" width="90" height="25" rx="4" fill="#6366f1" opacity="0.7"/><text x="100" y="137" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Container A</text><rect x="55" y="150" width="90" height="25" rx="4" fill="#a855f7" opacity="0.7"/><text x="100" y="167" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Container B</text><rect x="240" y="90" width="120" height="110" rx="8" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="300" y="110" text-anchor="middle" fill="#3b82f6" font-size="11" font-family="system-ui">Node 2</text><rect x="255" y="120" width="90" height="25" rx="4" fill="#2dd4bf" opacity="0.7"/><text x="300" y="137" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Container C</text><rect x="255" y="150" width="90" height="25" rx="4" fill="#6366f1" opacity="0.7"/><text x="300" y="167" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Container A</text><rect x="440" y="90" width="120" height="110" rx="8" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="500" y="110" text-anchor="middle" fill="#3b82f6" font-size="11" font-family="system-ui">Node 3</text><rect x="455" y="120" width="90" height="25" rx="4" fill="#a855f7" opacity="0.7"/><text x="500" y="137" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Container B</text><rect x="455" y="150" width="90" height="25" rx="4" fill="#f59e0b" opacity="0.7"/><text x="500" y="167" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Container D</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Container orchestration distributes workloads across multiple nodes for resilience and scale.</p></div>

4. Kill Switch

Every production feature should have a kill switch:

const enablePayments = await client.getBooleanValue('payments-enabled', true);
if (!enablePayments) {
  return res.status(503).json({
    message: 'Payment processing is temporarily unavailable'
  });
}

If your payment provider has an outage, toggle one flag instead of deploying code.

5. Configuration Flag (String/Number)

Use flags for runtime configuration:

const maxUploadSize = await client.getNumberValue('max-upload-mb', 10);
const maintenanceMessage = await client.getStringValue(
  'maintenance-banner',
  ''
);

Feature Flag Best Practices

Naming Convention

Use a consistent naming convention:

{team}.{scope}.{feature}

Examples:

checkout.payment.apple-pay
dashboard.analytics.new-charts
api.rate-limit.premium-tier

Flag Lifecycle

Every flag should have an expiration plan:

Phase
Duration
Action

|-------|----------|--------|

Development
Days
Flag created, default off
Canary
1-2 weeks
Rolling out to segments
GA
1 week
100% enabled
Cleanup
Sprint after GA
Remove flag and dead code

The most common mistake: never cleaning up flags. Stale flags accumulate and become technical debt. At TechSaaS, we set calendar reminders to remove flags 2 weeks after they reach 100% rollout.

Testing with Flags

Test both flag states in your CI pipeline:

describe('Checkout Flow', () => {
  test('renders new checkout when flag is on', async () => {
    mockFlagValue('new-checkout-flow', true);
    const result = render(<Checkout />);
    expect(result.getByText('Express Checkout')).toBeTruthy();
  });

  test('renders legacy checkout when flag is off', async () => {
    mockFlagValue('new-checkout-flow', false);
    const result = render(<Checkout />);
    expect(result.getByText('Standard Checkout')).toBeTruthy();
  });
});

<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 200" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="200" rx="12" fill="#1a1a2e"/><rect x="30" y="30" width="100" height="130" rx="6" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="80" y="55" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="monospace">docker-</text><text x="80" y="70" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="monospace">compose</text><text x="80" y="85" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="monospace">.yml</text><line x1="45" y1="95" x2="115" y2="95" stroke="#3b82f6" stroke-width="0.5" opacity="0.5"/><rect x="50" y="105" width="50" height="8" rx="2" fill="#94a3b8" opacity="0.3"/><rect x="50" y="118" width="60" height="8" rx="2" fill="#94a3b8" opacity="0.3"/><rect x="50" y="131" width="40" height="8" rx="2" fill="#94a3b8" opacity="0.3"/><path d="M135,95 L175,95" stroke="#e2e8f0" stroke-width="2" marker-end="url(#arrow2)"/><defs><marker id="arrow2" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><rect x="180" y="20" width="130" height="35" rx="6" fill="#6366f1" opacity="0.85"/><text x="245" y="42" text-anchor="middle" fill="#ffffff" font-size="11" font-family="system-ui">Web App</text><rect x="180" y="62" width="130" height="35" rx="6" fill="#a855f7" opacity="0.85"/><text x="245" y="84" text-anchor="middle" fill="#ffffff" font-size="11" font-family="system-ui">API Server</text><rect x="180" y="104" width="130" height="35" rx="6" fill="#2dd4bf" opacity="0.85"/><text x="245" y="126" text-anchor="middle" fill="#1a1a2e" font-size="11" font-family="system-ui">Database</text><rect x="180" y="146" width="130" height="35" rx="6" fill="#f59e0b" opacity="0.85"/><text x="245" y="168" text-anchor="middle" fill="#1a1a2e" font-size="11" font-family="system-ui">Cache</text><rect x="370" y="40" width="200" height="130" rx="8" fill="none" stroke="#e2e8f0" stroke-width="1" stroke-dasharray="5,4"/><text x="470" y="62" text-anchor="middle" fill="#e2e8f0" font-size="10" font-family="system-ui">Docker Network</text><line x1="310" y1="37" x2="390" y2="80" stroke="#94a3b8" stroke-width="1" opacity="0.5"/><line x1="310" y1="79" x2="390" y2="100" stroke="#94a3b8" stroke-width="1" opacity="0.5"/><line x1="310" y1="121" x2="390" y2="120" stroke="#94a3b8" stroke-width="1" opacity="0.5"/><line x1="310" y1="163" x2="390" y2="140" stroke="#94a3b8" stroke-width="1" opacity="0.5"/><circle cx="400" cy="80" r="5" fill="#6366f1"/><circle cx="400" cy="100" r="5" fill="#a855f7"/><circle cx="400" cy="120" r="5" fill="#2dd4bf"/><circle cx="400" cy="140" r="5" fill="#f59e0b"/><text x="470" y="85" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">:3000</text><text x="470" y="105" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">:8080</text><text x="470" y="125" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">:5432</text><text x="470" y="145" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">:6379</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Docker Compose defines your entire application stack in a single YAML file.</p></div>

Provider Comparison

Feature
Flagsmith
LaunchDarkly
PostHog
Unleash

|---------|-----------|--------------|---------|---------|

Self-hosted
Yes (free)
No
Yes (free)
Yes (free)
Cloud hosted
Yes
Yes
Yes
Yes
OpenFeature
Yes
Yes
Yes
Yes
A/B testing
Yes
Yes
Yes (built-in)
Limited
Pricing
Free self-hosted
$$$
Free self-hosted
Free self-hosted
UI quality
Good
Excellent
Good
Basic
SDK languages
15+
25+
10+
15+
Edge evaluation
Yes
Yes
No
No

For self-hosted infrastructure like we build at TechSaaS, Flagsmith is the clear winner. It is free, open-source, and runs as a single container with PostgreSQL.

Feature flags are a foundational DevOps practice. If you are still using long-lived feature branches and big-bang releases, adopting feature flags will transform your deployment confidence. Start with a kill switch for your most critical feature, then expand from there.

#feature-flags#openfeature#flagsmith#deployment#trunk-based-development

Need help with devops?

TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.