User-managed feature flags with Flipt - Part 1

Jan ŠvejdaJan Švejda
15 min read

Once a business takes continuous deployment seriously, it may reach a point where separating the release and deployment process starts to present a real blocker. If done well though, this can be an enabler for higher performance. Some might go as far as to consider this separation a prerequisite for deploying continuously. For example, if you have aligned schedules with the rest of the business, you typically cannot launch a feature immediately after development finishes, but would have synchronize, hampering any other work you need to do. Furthermore, if you have the luck of having feature-hungry power users, you will find that letting them beta test features allows for a faster feedback loop and helps you deliver more value for all users.

In this article, we will take a look into:

  1. Configuring a Docker image for Flipt with local data.

  2. Integrating Flipt into your GitOps practices.

  3. Letting users manage a subset of the flags on their own.

Feature flags, toggles, switches… which one is it?

There can be a certain confusion sometimes regarding each of these terms. Let us briefly summarize the meaning of each.

  • A feature flag is the most generic of these - it is a configuration item that lets you configure a feature in a specific environment. This does not have to be a boolean, but also a string, an integer etc. It allows fine control on how the feature works allowing you to test the feature beforehand.

  • Feature toggles, on the other hand, are simpler and are typically limited to boolean values on/off. Depending on your needs, this may control features per environment, tenant or user.

  • Feature switches are synonymous with feature toggles. Sparingly, they may refer to toggles which have a contractual binding to them. So, if a user does not pay for feature X, then the appropriate feature switch for this user would be off.

Flipt, open-source feature flagging

Before you go and implement your own feature flagging solution, it is worthwhile to research what is out there so that you focus on what is important and ideally have room to grow if you need more functionality, analytics, or integrations. This is where Flipt comes in as it represents one of the most popular open-source choices for feature flagging. Crucially, it integrates with OpenFeature, a standard steadily rising in popularity that provides an SDK to integrate feature flagging tools into your code. Flipt has both a worry-free cloud version and a full-featured self-hosted one. In this article, we will look into how the latter can be integrated into a microservice-oriented platform architecture. Have a look at a short example from Flipt’s homepage how you can use it to resolve if a feature “New UI” is enabled:

// Configure OpenFeature to use your Flipt instance
OpenFeatureAPI.getInstance().setProviderAndWait("sync", fliptProvider);
var client = OpenFeatureAPI.getInstance().getClient("sync");

// Prepare information to send with the flag evaluation request
var evaluationContext = new MutableContext();
evaluationContext.setTargetingKey("user-123");

// Evaluate if feature "New UI" is enabled by resolving the appropriate flag
var value = fliptProvider.getBooleanEvaluation("new-ui-enabled", false, evaluationContext).getValue());

Using Flipt as the core of your feature flagging strategy

To show how Flipt can be integrated, we shall make several assumptions about the architecture which will guide the solution.

  1. Multiple development teams - your organization consists of multiple development teams each being responsible for a subset of the services which make up your application.

  2. Multiple source code repositories and development teams - teams in your organization have dedicated Git repositories, there is no monorepo.

  3. Multi-tenancy - your applications are multi-tenant, meaning you have only one production environment, but each customer has dedicated data space in your application, which only the users for that customer have access to.

  4. Tenant admins can manage toggles - each tenant has admin users which can enable or disable features, giving them the possibility to preview these early on.

  5. Containerization - you want to host Flipt yourself and can run containers in your environment, be it a cloud or on-premise environment. Flipt supports many other kinds of self-hosted operations. Take a look in the docs.

With that out of the way, let’s dive right into it!

Configuration

Flipt can store feature flag configuration using multiple backends including relational databases, object storage, git and more. There lies beauty in simplicity though, so let’s take advantage of using YAML files as the source. I will explain later in section Flipt and GitOps how this helps developers change and keep track of things.

With that said, initiate a new Git repository or if you prefer, add a subfolder to your monorepo.

├── README.md
└── src
    ├── config.yml
    ├── Dockerfile
    ├── features
    │   ├── features.yml
    │   └── project-x.features.yml
    └── test
        └── rest
            └── evaluate.http

Under src, we will keep the actual configuration, such as the Dockerfile and feature flags. From the top of the src folder, we have:

  • config.yml - Flipt configuration, see the documentation for all available options.

  • Dockerfile - we build an image that, immediately after starting, will be ready to evaluate current feature flags.

  • features - folder containing all the feature flag configuration files. Here you can place files containing your desired set of feature flags in their particular namespace. This is where teams would go update their flags which are currently available in their application.

Let’s have a look at each of these items in more detail.

Side note - build system
I used Gradle as a build system for our - if you are interested how that helps you automate and setup CI, let me know in the comments, I will be happy to write a follow-up article. Ideally, you should have execution tasks at least for building, testing, pushing an image and for starting a debugging instance locally.

Flipt configuration - config.yml

As already mentioned, we are leveraging Flipt’s ability to import files with feature flags instead of using a dedicated database. This has the advantage of Flipt immediately becoming read-only - in this mode it is not possible to edit or add any flags, which is exactly in the spirit of GitOps. Manual changes in the deployed application would not have been tracked by Git and would not be persisted either when using the local storage backend (you would need a database backend for that).

In the file below, apart from the obligatory configuration of the storage backend, you will also find settings for the log format (JSON), and both tracing and metrics, which support OpenTelemetry. Logs do not yet support OpenTelemetry:

storage:
  type: local # makes Flipt read-only
  local:
    path: "/data"

########################################################
## Not strictly needed, but useful for production use ##
########################################################
cache:
  enabled: true
  backend: memory
  ttl: 5m # items older than 5 minutes will be marked as expired
  memory:
    eviction_interval: 2m # expired items will be evicted from the cache every 2 minutes
log:
  encoding: json
  level: info
tracing:
  enabled: ${OTEL_EXPORTER_OTLP_ENABLED}
  exporter: ${OTEL_TRACES_EXPORTER}
  otlp:
    endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}
metrics:
  enabled: ${OTEL_EXPORTER_OTLP_ENABLED}
  exporter: ${OTEL_METRICS_EXPORTER}
  otlp:
    endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT}

# auth...

Dockerfile configuration

The Dockerfile will be very simple, but allows us to add the configuration of our specific feature flags into the image. Similarly, we include the config.yml file to configure Flipt’s behavior like authentication, logging, tracing etc. Flipt reads environment variables for many of these configurations as well.

FROM flipt/flipt:latest

# all feature files of the form '[*.]features.yml' will be imported
COPY features /data
COPY config.yml /config.yml

# where the homepage and REST API are available:
EXPOSE 8080
# for gRPC API this is:
EXPOSE 9000

USER flipt
CMD ["/flipt", "--config", "/config.yml"]

Running a Docker build will result in an image which you can start as a server listening on port 8080 (you of course have to map it as always). The standard way to build the image and run a container is as you would expect:

# Build
docker build -t my-flipt-image:latest .

# Run
docker run --rm -p "8080:8080" my-flipt-image:latest

Running it you should see output in your terminal similar to:

    _________       __
   / ____/ (_)___  / /_
  / /_  / / / __ \/ __/
 / __/ / / / /_/ / /_
/_/   /_/_/ .___/\__/
         /_/

Version: v1.56.0
Commit: 080bac92fe2c452681d5bd5330fe57cd7d902183
Build Date: 2025-03-17T19:13:58Z
Go Version: go1.24.1
OS/Arch: linux/arm64

You are currently running the latest version of Flipt [v1.56.0]!

API: http://0.0.0.0:8080/api/v1
UI: http://0.0.0.0:8080

Feature flag configurations

Flipt will import all feature flag files under the folder features - during build docker copies this folder into the path /data in the image. We have configured Flipt to read all data from there. Feature flag files have to follow the pattern below otherwise Flipt ignores them:

  • **/features.yaml

  • **/features.yml

  • **/*.features.yaml

  • **/*.features.yml

Flipt uses namespaces to organize feature flags. I would recommend you to map these 1:1 to the files, so that namespace alpha has an appropriate file alpha.features.yml. With multiple teams, you will want to make sure that each can work separately to prevent merge conflicts. Therefore, each of your teams should have one or more namespaces. If you have teams alpha, beta, gamma, then each would have a feature flag file alpha.features.yml, beta.features.yml and gamma.features.yml. As an example, you can add flags like this:

version: "1.2"
namespace: alpha
flags:
  - key: feature-xyz
    name: Feature XYZ is in testing
    type: BOOLEAN_FLAG_TYPE
    enabled: false
    rollouts:
      - segment:
          key: internal-users
          value: true
      - threshold:
          percentage: 20
          value: true
segments:
  - key: internal-users
    name: Internal Users
    match_type: ANY_MATCH_TYPE
    constraints:
      - property: organization
        operator: eq
        value: internal
        type: STRING_COMPARISON_TYPE

With this, you have configured a new flag feature-xyz and a segment for internal users, where this feature is on by default. The segment internal-users is defined by a property which has to be equal to internal. When in an evaluation request the organization property equals internal, the response will be true. Otherwise, it will be true 20% of the time as we have defined a threshold rollout. You can test it out locally by sending a request to Flipt’s evaluation API:

POST http://localhost:8080/evaluate/v1/boolean
Content-Type: application/json

{
  "namespaceKey": "alpha",
  "flagKey": "feature-xyz",
  "entityId": "user-123",
  "context": {
    "organization": "internal"
  }
}

Notice that we have used the user ID as the entityId, meaning that the rollout distribution (threshold 20%) for requests outside the internal organization will be based on the unique users.

Flipt and GitOps

With a Docker image ready to go, you can deploy it and start testing. However, we want our organization to use it productively across multiple teams and applications (see assumptions at the beginning of the article). To add operational complexity to every team, we should run it as a platform service for everyone. So, all teams integrate with a single instance, meaning we need to ensure that no changes break another team’s feature flags! Therefore, we want to track what is going on and leverage Continuous Integration and Deployment, testing flags automatically and requiring same development quality standards as for application development. Good thing is, we can achieve this with GitOps! It is the reason why in the previous section we already prepared a Git repository and chose simple files as the source of truth for feature flags.

The workflow to change, add or remove feature flags is very simple:

First, a contributor commits changes to the main branch, which are immediately tested in a CI pipeline. If the test fails, the contributor has to remediate that - the sooner, the better, as it might block others in making changes to their feature flags. If the test stage succeeds, then the new version is deployed right away. You should make sure to use zero-downtime deployments - rolling out the change successively to prevent unwanted downtime. (We won’t go into that here, Flipt is stateless so this should not be a problem to achieve with most commonly used container orchestration platforms like Kubernetes, Apache Mesos, HashiCorp Nomad or others.)

For example, if team alpha want to create a feature flag feature-new, they open the file for the alpha team’s namespace and add it as a new flag:

diff --git i/src/features/alpha.features.yml w/src/features/alpha.features.yml
index c736e87..fa47c9b 100644
--- i/src/features/alpha.features.yml
+++ w/src/features/alpha.features.yml
@@ -12,6 +12,10 @@ flags:
       - threshold:
           percentage: 20
           value: true
+  - key: feature-new
+    name: New feature
+    type: BOOLEAN_FLAG_TYPE
+    enabled: false
 segments:
   - key: internal-users
     name: Internal Users

You can add pre-push hooks to ensure changes are tested locally before pushed to main. The push to origin triggers a build and deploy pipeline which publishes the flag.

Enabling your users to configure feature toggles themselves

Now we come to the part of how you can integrate Flipt with your application and provide a self-service to users so that they can enable or disable toggles (controlling whether a feature is on/off) in their own tenant. We explicitly only use toggles - nothing more complex than an on/off switch - so that users need to do only a simple decision.

What we need to consider - providing this, even if just toggles, a new set of conditions for feature toggle evaluation is configured by a very different group of people who mostly come from outside your organization. This configuration has to now interact with Flipt’s logic of resolving whether a feature toggle is on or not. Let us explicitly state the two main groups of people using feature toggles:

  1. Employees or contractors - your people want to slowly roll out features to ensure the stability of your application and to get early feedback. This group of people will have access to source code and use the GitOps flow to manage all feature flags. Generally, they configure conditions for feature flags for everyone or a bigger subset of tenants/users - e.g., all internal users, testers, developers etc.

  2. Users (admins) of tenants in your application - users or companies who buy your product and want to be productive with it. Power users, partners or customers eager to test new features early. Any configuration has to be done in the scope of their tenant (or depending on your setup, an account) without affecting others. Both your organization and they need to be aware that any provided feature toggle may lead to unforeseen consequences when enabled. On top of that, you especially need to consider that these users might (intentionally or not) explore the edge cases and security weak spots.

In light of these two groups, we need to ensure the architecture reflects the different needs. The former group is set up quite well with the GitOps flow described above. As depicted below, we will add a new component to our platform to cater for the latter where we will store configured feature toggles in a database table per tenant. Requests to evaluate a feature toggle will consider this configuration and all other feature flags will be simply forwarded to Flipt. Let us call this component Feature Manager.

Required APIs of Feature Manager

First, we need an API for evaluating whether a toggle is enabled or not. We will keep it simple and wrap the existing evaluation REST API of Flipt, putting the Self-Service in-front of all evaluation requests, no matter if flags or toggles. We shall implement absolutely the same API specification as Flipt. This has actually the benefit of fewer complexity to maintain. If we would place the Feature Manager next to Flipt as a standalone service, we would have two APIs, one specific to the available feature toggles and one for everything else - the Flipt APIs. All clients would have to know both of these services and their APIs. Instead, we have just one and keep compatibility with all current and future tools integrated with Flipt, like OpenFeature.

The Feature Manager performs evaluation of a flag as follows:

  1. Call the evaluation API provided by a wrapper service and include in body: a batch of feature flags to resolve, tenant, user ID.

  2. Filter for feature toggles. Perform a look up in the database (scoped for this tenant) to check if there is any configuration. If yes, resolve it immediately and remove it from the batch of flags.

  3. Resolve the remaining features by calling the Flipt evaluation API.

  4. Return the consolidated result.

Second API we need is for configuring and managing feature toggles available to users. Let’s call it the Feature Toggle Management API. This API must have proper access control to allow only administrators to use it. In general, we need read, update and delete endpoints for feature toggles. There is no need for introducing an endpoint for creating, because the availability of a toggle will be managed by the product - not via APIs, but generated from the same files used as sources for Flipt. How this generation works will be shown in Part 2 of this article. The specification of the Toggle Management API can be as follows:

openapi: 3.0.0
info:
  title: Feature Toggle Management API
  description: API for configuring and managing feature toggles available to users.
  version: "1.0.0"
servers:
  - url: https://tenant.app.com/api/v1
paths:
  /feature-toggles:
    get:
      summary: List all available feature toggles
      description: Returns a list of all feature toggles available in the system.
      parameters:
        - name: namespace
          in: query
          description: Optional namespace for scoping toggles.
          required: false
          schema:
            type: string
            default: default
      responses:
        '200':
          description: A JSON array of feature toggles.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FeatureToggle'
  /feature-toggles/{toggleId}:
    parameters:
      - name: toggleId
        in: path
        description: Unique identifier of the feature toggle.
        required: true
        schema:
          type: string
      - name: namespace
        in: query
        description: Optional namespace for scoping toggles.
        required: false
        schema:
          type: string
          default: default
    get:
      summary: Get a feature toggle by ID
      description: Retrieves the details of a specific feature toggle.
      responses:
        '200':
          description: Feature toggle details.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureToggle'
        '404':
          description: Feature toggle not found.
    put:
      summary: Update a feature toggle
      description: Updates the details of a specific feature toggle.
      requestBody:
        required: true
        description: Set a new value for the feature toggle.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FeatureToggleUpdate'
      responses:
        '200':
          description: Feature toggle updated successfully.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureToggle'
        '400':
          description: Invalid input.
        '404':
          description: Feature toggle not found.
    delete:
      summary: Delete a feature toggle
      description: Deletes a feature toggle configuration from this tenant.
      responses:
        '204':
          description: Feature toggle configuration deleted successfully.
        '404':
          description: Feature toggle not found.
components:
  schemas:
    FeatureToggle:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the toggle.
        name:
          type: string
          description: Name of the feature toggle.
        enabled:
          type: boolean
          description: Indicates whether the feature is enabled. Null if not configured.
        description:
          type: string
          description: A brief description of what the toggle controls.
      required:
        - id
        - name
        - enabled
    FeatureToggleUpdate:
      type: object
      properties:
        enabled:
          type: boolean
          description: Updated state of the feature toggle.
      required:
        - enabled

Summary

This article explains how to use Flipt, an open-source feature flag system, as part of a GitOps workflow. It shows how to manage feature flags via YAML files in Git, build them into Docker images, and deploy with CI/CD. It also introduces a self-service API so tenant admins can manage their own toggles, while internal teams keep control through Git. The setup ensures clean, consistent flag management across teams and environments.

In Part 2, we will take a look at safely integrating the defined feature flags in YAML with your clients. Of importance will be especially ensuring there is no drift between the available feature flags and the clients by generating typed source code out of the YAML files.

References

0
Subscribe to my newsletter

Read articles from Jan Švejda directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Jan Švejda
Jan Švejda