<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[DevOps, Teams and Architecture]]></title><description><![CDATA[Thoughts about team collaboration and architecture for a thriving DevOps culture.]]></description><link>https://blog.jansvejda.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 14:46:33 GMT</lastBuildDate><atom:link href="https://blog.jansvejda.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Move Fast, Break Nothing*: Feature Flags as Enabler of Continuous Deployment]]></title><description><![CDATA[The Conflict of Speed vs. Stability
If there are axioms in software development, then the duality of users wanting more features and a bug-free, always-available system must be one of them. An unstable system is the quickest way to churn users, but w...]]></description><link>https://blog.jansvejda.com/move-fast-break-nothing</link><guid isPermaLink="true">https://blog.jansvejda.com/move-fast-break-nothing</guid><category><![CDATA[continuous deployment]]></category><category><![CDATA[  feature flags]]></category><category><![CDATA[Devops]]></category><category><![CDATA[continuous delivery]]></category><dc:creator><![CDATA[Jan Švejda]]></dc:creator><pubDate>Sun, 25 Jan 2026 14:16:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/tvfU4dq6wjY/upload/274dd45c3645e2411cfbbf79aa8eecfc.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-the-conflict-of-speed-vs-stability">The Conflict of Speed vs. Stability</h2>
<p>If there are axioms in software development, then the duality of users wanting more features <em>and</em> a bug-free, always-available system must be one of them. An unstable system is the quickest way to churn users, but without features, you are bound to get disrupted by innovative competitors. So, there is no way around it: you have to release changes. The trick is ensuring these changes have the desired effect and are well-accepted by users. And challenging things? Those require a strategy.</p>
<p>In our case, we fight for market share with companies sometimes 10x bigger than us. Instead of shying away, we turn this into a competitive advantage because we can do things differently. We can adapt and align the whole organization on a new vision much more quickly. To strengthen this edge, we decided to use <strong>Continuous Deployment (CD)</strong> for all changes to our product, a B2B SaaS platform for process digitalization.</p>
<p>The premise of CD, as you might read on social media or in explanatory videos, is usually this:</p>
<ol>
<li><p>Code a new change.</p>
</li>
<li><p>Run automated checks.</p>
</li>
<li><p>Once passing, deploy the new version to production.</p>
</li>
</ol>
<p>This sounds like the antithesis of stability. As with any design, product, or even art, the first version of anything new requires feedback and rework to stand the test of its beholders.</p>
<p>So you say: <em>“We should deploy the first version directly to all users?!”</em></p>
<p>The answer is <strong>no</strong>, you should not. You should have a strong release process leveraging <strong>feature flags</strong> to decouple a release from deployment. And just as importantly, you should align this process with your documentation, marketing, and customer communication. You will understand how to adopt the 5 segment release process after reading this article.</p>
<h2 id="heading-why-we-use-feature-flags">Why We Use Feature Flags</h2>
<p>Feature flags allow us to get features into production and into the hands of Product Owners, Presales, and early adopters without disrupting other users. We can iterate rapidly, testing and gathering feedback in the same production environment our users live in every day. We do this without a new version, everything is on <code>main</code>. This offers three major advantages:</p>
<ul>
<li><p><strong>Feedback without High Complexity:</strong> Users can test features early, yet we have no maintenance of extra environments since we always keeping only a single version up and running.</p>
</li>
<li><p><strong>Minimized Risk:</strong> We can deploy multiple small changes instead of big bang deployments, which reduces risk exposure and emphasizes steady flow of work within the organization.</p>
</li>
<li><p><strong>Release Control:</strong> The teams decide on their own when a feature is ready. They own their flags and the entire release process. They update documentation and set release dates without the pressure of a deadline because releasing a feature is just a single boolean configuration.</p>
</li>
</ul>
<p>Both our frontend and backend components are integrated with <a target="_blank" href="https://www.flipt.io/"><strong>Flipt</strong></a>, a flag management tool. With a single boolean configuration, we manage the narrative and control exactly <em>when</em> a feature appears to users with a single boolean configuration. It also puts an end to the "Has this been enabled in environment X?" questions; the flag state tells the story.</p>
<blockquote>
<p>Feature flags vs. Tiers - it is important to distinguish flags from product tiers. Flags play their role strictly in release control, not for long-term configuration of customer licensing, contractual agreements or usage based pricing. Flags have to be removed not long after a feature has been released as the codebase would otherwise get littered with if-else statements.</p>
</blockquote>
<h2 id="heading-feature-flag-lifecycle-and-the-five-user-segments">Feature Flag Lifecycle and the Five User Segments</h2>
<p>To manage risk effectively, you cannot just "flip a switch" and be done with it. That would destroy the whole purpose of having control. We found using a ring-based deployment model in expanding user segments works well. Progressively, as a change matures, you enlarge the subset of users who see it. In other words, you limit the initial blast radius, gather data and feedback and only expand once certain of the stability and value of the change. Think of it as a funnel that gets bigger and bigger as the feature matures:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769349776293/ea60c2be-e324-4f83-bd64-6f3522666716.jpeg" alt class="image--center mx-auto" /></p>
<p>Your mileage may vary, but we found it effective for a feature to go through five consecutive segments. These build up on each other as every segment includes all users in the previous segments:</p>
<h3 id="heading-development">Development</h3>
<p>The moment code is merged, it’s live in the <code>development</code> segment. This includes the developer environments and select internal environments used for testing and training. Here, the goal is immediate feedback for the developers themselves. It’s about seeing the code run in a safe environment without impact if anything goes sideways.</p>
<h3 id="heading-validation">Validation</h3>
<p>A feature moves to <code>validation</code> once it has been presented in a Sprint Review and the Product Owner has given the green light. Any bugs found during the initial dev phase must be resolved here. This is where you ensure the feature actually does what it was intended to do. The <code>validation</code> segment includes all internal testing and training environments.</p>
<h3 id="heading-preview">Preview</h3>
<p>Next, promote to <code>preview</code>. This opens the feature to select non-live tenants in production used for demos and manual or automated tests in production. This is a crucial step - it is now enabled on production infrastructure with production-like data, but without the risk of impacting a customer’s daily operations.</p>
<h3 id="heading-release-candidate">Release Candidate</h3>
<p>Before a full release, we enter the <code>release-candidate</code> segment. This is the first time a change faces users who use the product for actual work. In our case, we use our own internal environments used by the whole company or a department. It’s the final "smoke test" to ensure that everything holds up under real-life usage and should happen a few days in advance of a release.</p>
<h3 id="heading-release">Release</h3>
<p>Finally, a change reaches the <code>release</code> segment. It is enabled for all tenants in production. But even here, the work isn't "finished." You should keep the toggle active for a short period - this acts as an emergency brake. If something unexpected happens at scale, you can roll back the feature almost instantly without having to redeploy the entire application.</p>
<p>The below diagram depicts the 5 segments:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769349902765/34435897-32c0-49cc-8bce-fa07079ba3f9.png" alt class="image--center mx-auto" /></p>
<p>After all these segments, the feature flag is removed from the application code, including obsolete code and finally, the feature flag is removed as well. It is vital to remove the flag only after you do it in the application code. Otherwise, you will effectively disable the feature for all users.</p>
<h2 id="heading-communication-during-the-lifecycle">Communication During the Lifecycle</h2>
<p>A feature flag strategy is 50% technical and 50% communication. With changes, users expect information how these improve and impact their ways of using the software. This is not always necessary, many changes are self-explanatory and do what users expect. Nonetheless, more complex features require documentation and, even more so, breaking changes require clear explanation and instructions. Whatever the information and its form, timing is crucial. The effort you put into sending information or updating documentation will be lost, if you inform users about a new feature after it had been already deployed.</p>
<p>On the other hand, if you are informing ahead that you are about to release something new, then users expect to know when this happens - so you have to <em>commit to a date</em>. What if you don’t make it though? What if you must take care of multiple incidents in the meantime or your build infrastructure does not cooperate? All this can happen if your release process is complicated. Your final act of releasing should therefore be trivial to limit the amount of things that can break. We made it so, that releasing a feature is a single flip of a boolean. Like this, the risk of not meeting a communicated release date is minimal.</p>
<p>Furthermore, internal users and stakeholders will want to know about work in progress. The 5 segment strategy therefore guides when and how to provide information during each of the segments:</p>
<ul>
<li><p><strong>Development:</strong> Here communication is purely internal and within the team or closest stakeholders.</p>
</li>
<li><p><strong>Validation:</strong> First tests have been successful so ask for feedback early. Provide an overview using mockups, slides or first working versions, e.g., in a Sprint Review, or live demos. Ask internal users to test and provide feedback.</p>
</li>
<li><p><strong>Preview:</strong> Now that the feature has taken shape you should show the intention of releasing it. Update any "Coming Soon" section in docs, ask for final tests and make sure people outside your team use it. Release first versions of the documentation around internally and improve based on input.</p>
</li>
<li><p><strong>Release Candidate/Release:</strong> Your feature is ready to step into the limelight. Update the official Changelog and documentation (either in app or separate). Make broad announcements using email, notifications or similar channels. Explain in a webinar or workshop. The particular communication strategy differs between features, but Changelog is the minimum.</p>
</li>
</ul>
<p>A successful release strategy is 50% technical and 50% communication. Users expect to understand how changes will improve or impact their workflow. While many updates are self-explanatory, complex features require documentation, and breaking changes demand clear explanations and instructions.</p>
<p>Regardless of the format, <strong>timing is crucial.</strong> The effort spent on communication or documentation is wasted if users are frustrated by a new feature that has already appeared in their environment.</p>
<h3 id="heading-managing-expectations-and-risks">Managing Expectations and Risks</h3>
<p>If you inform users ahead of time that a release is coming, you must commit to a date. However, real-world obstacles—like high-priority incidents or infrastructure failures—can jeopardize those commitments. To mitigate this, the act of "releasing" must be trivial. By utilizing feature flags, we have ensured that releasing a feature is as simple as flipping a boolean. This minimizes the risk of missing a communicated release date due to deployment complexities.</p>
<h3 id="heading-the-5-segment-communication-strategy">The 5-Segment Communication Strategy</h3>
<p>Stakeholders and internal users need visibility into work-in-progress. Our five-segment strategy guides how and when to provide information:</p>
<ul>
<li><p><strong>Development:</strong> Communication remains internal to the team and immediate stakeholders.</p>
</li>
<li><p><strong>Validation:</strong> Once initial tests succeed, seek early feedback. Provide overviews via mockups, slides, or early working versions (e.g., during Sprint Reviews or live demos). Encourage internal users to test and provide input.</p>
</li>
<li><p><strong>Preview:</strong> As the feature takes its final shape, signal the intent to release. Update "Coming Soon" documentation, request final testing from users outside the core team, and refine internal documentation based on their feedback.</p>
</li>
<li><p><strong>Release Candidate &amp; Release:</strong> Your feature is ready for the limelight. Update the official Changelog and external documentation. Use broad channels - such as email, in-app notifications, or webinars - to accompany the rollout. Communicate ahead or together with the release. While the specific strategy varies by feature, an entry in the Changelog is the minimum requirement. Important: <strong>Don’t skip the release candidate segment</strong>, we have had cases of critical bugs being found a day before planned rollout.</p>
</li>
</ul>
<h2 id="heading-managing-complexity">Managing Complexity</h2>
<p>Feature flags do not come free. You need to branch at the appropriate places in code to ensure your unfinished changes do not leak. That can get out of hand quickly without a plan. If you are dealing with quantities of unreleased features, this strategy is likely not for you. Too many flags is deadly both for codebases and velocity, they must be short-lived. Removing a flag should be included in the definition of done for features, but you also need to ensure there are not too many features in progress to begin with.</p>
<blockquote>
<p>If you are already at the point of having too many flags, a viable strategy is to allow adding a new feature flag only if two existing ones have been removed. This way you pay off technical debt gradually.</p>
</blockquote>
<p>Another problem is if flags in your flag management tool drift away from the codebase. Then you have to go through each and every one to resolve the issue. There is a better way though - let the compiler do the work! Instead of:</p>
<pre><code class="lang-kotlin"><span class="hljs-keyword">if</span> (featureState[<span class="hljs-string">"fresh-design"</span>]?.enabled || <span class="hljs-literal">false</span>) {
  <span class="hljs-comment">// new logic</span>
} <span class="hljs-keyword">else</span> {
  ...
}
</code></pre>
<p>Generate an enum from flag definitions. You will get compilation errors unless the flag exists:</p>
<pre><code class="lang-kotlin"><span class="hljs-comment">// start generated</span>
<span class="hljs-keyword">enum</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BoolFeatureFlags</span></span>(<span class="hljs-keyword">val</span> key: String) {
    FRESH_DESIGN(<span class="hljs-string">"fresh-design"</span>),
    V2_API(<span class="hljs-string">"v2-api"</span>),
    ...
}
<span class="hljs-comment">// end generated</span>

<span class="hljs-keyword">if</span> (featureState.enabled(FRESH_DESIGN)) {
  <span class="hljs-comment">// new logic</span>
} <span class="hljs-keyword">else</span> {
  ...
}
</code></pre>
<p>The workflow then looks something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769350339940/03a5ef6c-0bbe-4e42-9adb-0ee9d7bf2d3e.jpeg" alt class="image--center mx-auto" /></p>
<p>Each flag management tool has a different format, so your mileage may vary, but a simple script will do the job. That way, you prevent different sets of feature flags. If you want to go the extra mile, an idea courtesy of <a target="_blank" href="https://github.com/MaSch0212">Marc</a>, is to have the code generator skip flags that are marked as <code>deprecated</code>, e.g., with a description prefix <code>[DEPRECATED]</code>. Then you can very safely remove flags like so:</p>
<ol>
<li><p>Add <code>[DEPRECATED]</code> to description in the flag management tool. This triggers compiler errors in all places the flag is used.</p>
</li>
<li><p>Update code to remove unneeded logic.</p>
</li>
<li><p>Deploy the application.</p>
</li>
<li><p><em>Then</em> delete the flag from the management tool.</p>
</li>
</ol>
<p>With this in place, your codebase won’t suffer.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Segmenting your users in up to five categories and consecutively enabling new changes one by one in each using feature flags gives you full control over the feature release process. By adhering to this rollout strategy, our team achieves:</p>
<ul>
<li><p>Higher and fully automated deployment frequency using Continuous Deployment.</p>
</li>
<li><p>Lower failure rate. Bugs are caught in early segments.</p>
</li>
<li><p>Better product-market fit. Features get very early feedback, even from customers.</p>
</li>
</ul>
<p>The trade-off for this strategy lies in higher code complexity. It is, however, well-manageable if you 1) focus on finishing features, 2) define a clear feature flag lifecycle, and 3) deeply integrate feature flag management with your codebase.</p>
<p>Let me know if you try or adapt this blueprint to your needs, looking forward to your thoughts!</p>
]]></content:encoded></item><item><title><![CDATA[User-managed feature flags with Flipt - Part 1]]></title><description><![CDATA[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...]]></description><link>https://blog.jansvejda.com/user-managed-feature-flags-with-flipt-part-1</link><guid isPermaLink="true">https://blog.jansvejda.com/user-managed-feature-flags-with-flipt-part-1</guid><category><![CDATA[flipt]]></category><category><![CDATA[  feature flags]]></category><category><![CDATA[Devops]]></category><category><![CDATA[architecture]]></category><dc:creator><![CDATA[Jan Švejda]]></dc:creator><pubDate>Tue, 08 Apr 2025 19:30:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/acKSt3THWKA/upload/4d8823832f8cd9a44d744fee09dd5392.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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.</p>
<p>In this article, we will take a look into:</p>
<ol>
<li><p>Configuring a Docker image for Flipt with local data.</p>
</li>
<li><p>Integrating Flipt into your GitOps practices.</p>
</li>
<li><p>Letting users manage a subset of the flags on their own.</p>
</li>
</ol>
<h3 id="heading-feature-flags-toggles-switches-which-one-is-it">Feature flags, toggles, switches… which one is it?</h3>
<p>There can be a certain confusion sometimes regarding each of these terms. Let us briefly summarize the meaning of each.</p>
<ul>
<li><p>A <em>feature flag</em> 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.</p>
</li>
<li><p><em>Feature toggles,</em> 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.</p>
</li>
<li><p><em>Feature switches</em> 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.</p>
</li>
</ul>
<h2 id="heading-flipt-open-source-feature-flagging">Flipt, open-source feature flagging</h2>
<p>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 <a target="_blank" href="https://www.flipt.io">Flipt</a> comes in as it represents one of the most popular open-source choices for feature flagging. Crucially, it integrates with <a target="_blank" href="https://openfeature.dev">OpenFeature</a>, 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 <a target="_blank" href="https://www.flipt.io">homepage</a> how you can use it to resolve if a feature “New UI” is enabled:</p>
<pre><code class="lang-java"><span class="hljs-comment">// Configure OpenFeature to use your Flipt instance</span>
OpenFeatureAPI.getInstance().setProviderAndWait(<span class="hljs-string">"sync"</span>, fliptProvider);
<span class="hljs-keyword">var</span> client = OpenFeatureAPI.getInstance().getClient(<span class="hljs-string">"sync"</span>);

<span class="hljs-comment">// Prepare information to send with the flag evaluation request</span>
<span class="hljs-keyword">var</span> evaluationContext = <span class="hljs-keyword">new</span> MutableContext();
evaluationContext.setTargetingKey(<span class="hljs-string">"user-123"</span>);

<span class="hljs-comment">// Evaluate if feature "New UI" is enabled by resolving the appropriate flag</span>
<span class="hljs-keyword">var</span> value = fliptProvider.getBooleanEvaluation(<span class="hljs-string">"new-ui-enabled"</span>, <span class="hljs-keyword">false</span>, evaluationContext).getValue());
</code></pre>
<h2 id="heading-using-flipt-as-the-core-of-your-feature-flagging-strategy">Using Flipt as the core of your feature flagging strategy</h2>
<p>To show how Flipt can be integrated, we shall make several assumptions about the architecture which will guide the solution.</p>
<ol>
<li><p><em>Multiple development teams</em> - your organization consists of multiple development teams each being responsible for a subset of the services which make up your application.</p>
</li>
<li><p><em>Multiple source code repositories and development teams</em> - teams in your organization have dedicated Git repositories, there is no monorepo.</p>
</li>
<li><p><em>Multi-tenancy</em> - 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.</p>
</li>
<li><p><em>Tenant admins can manage toggles</em> - each tenant has admin users which can enable or disable features, giving them the possibility to preview these early on.</p>
</li>
<li><p><em>Containerization</em> - 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 <a target="_blank" href="https://docs.flipt.io/self-hosted/overview">docs</a>.</p>
</li>
</ol>
<p>With that out of the way, let’s dive right into it!</p>
<h1 id="heading-configuration">Configuration</h1>
<p>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 <em>Flipt and GitOps</em> how this helps developers change and keep track of things.</p>
<p>With that said, initiate a new Git repository or if you prefer, add a subfolder to your monorepo.</p>
<pre><code class="lang-plaintext">├── README.md
└── src
    ├── config.yml
    ├── Dockerfile
    ├── features
    │   ├── features.yml
    │   └── project-x.features.yml
    └── test
        └── rest
            └── evaluate.http
</code></pre>
<p>Under <code>src</code>, we will keep the actual configuration, such as the Dockerfile and feature flags. From the top of the <code>src</code> folder, we have:</p>
<ul>
<li><p><code>config.yml</code> - Flipt configuration, see the documentation for all available options.</p>
</li>
<li><p><code>Dockerfile</code> - we build an image that, immediately after starting, will be ready to evaluate current feature flags.</p>
</li>
<li><p><code>features</code> - 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.</p>
</li>
</ul>
<p>Let’s have a look at each of these items in more detail.</p>
<details><summary>Side note - build system</summary><div data-type="detailsContent">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.</div></details>

<h3 id="heading-flipt-configuration-configyml">Flipt configuration - config.yml</h3>
<p>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 <code>local</code> storage backend (you would need a database backend for that).</p>
<p>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:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">storage:</span>
  <span class="hljs-attr">type:</span> <span class="hljs-string">local</span> <span class="hljs-comment"># makes Flipt read-only</span>
  <span class="hljs-attr">local:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">"/data"</span>

<span class="hljs-comment">########################################################</span>
<span class="hljs-comment">## Not strictly needed, but useful for production use ##</span>
<span class="hljs-comment">########################################################</span>
<span class="hljs-attr">cache:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">backend:</span> <span class="hljs-string">memory</span>
  <span class="hljs-attr">ttl:</span> <span class="hljs-string">5m</span> <span class="hljs-comment"># items older than 5 minutes will be marked as expired</span>
  <span class="hljs-attr">memory:</span>
    <span class="hljs-attr">eviction_interval:</span> <span class="hljs-string">2m</span> <span class="hljs-comment"># expired items will be evicted from the cache every 2 minutes</span>
<span class="hljs-attr">log:</span>
  <span class="hljs-attr">encoding:</span> <span class="hljs-string">json</span>
  <span class="hljs-attr">level:</span> <span class="hljs-string">info</span>
<span class="hljs-attr">tracing:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-string">${OTEL_EXPORTER_OTLP_ENABLED}</span>
  <span class="hljs-attr">exporter:</span> <span class="hljs-string">${OTEL_TRACES_EXPORTER}</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-string">${OTEL_EXPORTER_OTLP_ENDPOINT}</span>
<span class="hljs-attr">metrics:</span>
  <span class="hljs-attr">enabled:</span> <span class="hljs-string">${OTEL_EXPORTER_OTLP_ENABLED}</span>
  <span class="hljs-attr">exporter:</span> <span class="hljs-string">${OTEL_METRICS_EXPORTER}</span>
  <span class="hljs-attr">otlp:</span>
    <span class="hljs-attr">endpoint:</span> <span class="hljs-string">${OTEL_EXPORTER_OTLP_ENDPOINT}</span>

<span class="hljs-comment"># auth...</span>
</code></pre>
<h3 id="heading-dockerfile-configuration">Dockerfile configuration</h3>
<p>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 <code>config.yml</code> file to configure Flipt’s behavior like authentication, logging, tracing etc. Flipt reads environment variables for many of these configurations as well.</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> flipt/flipt:latest

<span class="hljs-comment"># all feature files of the form '[*.]features.yml' will be imported</span>
<span class="hljs-keyword">COPY</span><span class="bash"> features /data</span>
<span class="hljs-keyword">COPY</span><span class="bash"> config.yml /config.yml</span>

<span class="hljs-comment"># where the homepage and REST API are available:</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8080</span>
<span class="hljs-comment"># for gRPC API this is:</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">9000</span>

<span class="hljs-keyword">USER</span> flipt
<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"/flipt"</span>, <span class="hljs-string">"--config"</span>, <span class="hljs-string">"/config.yml"</span>]</span>
</code></pre>
<p>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:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Build</span>
docker build -t my-flipt-image:latest .

<span class="hljs-comment"># Run</span>
docker run --rm -p <span class="hljs-string">"8080:8080"</span> my-flipt-image:latest
</code></pre>
<p>Running it you should see output in your terminal similar to:</p>
<pre><code class="lang-plaintext">    _________       __
   / ____/ (_)___  / /_
  / /_  / / / __ \/ __/
 / __/ / / / /_/ / /_
/_/   /_/_/ .___/\__/
         /_/

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
</code></pre>
<h3 id="heading-feature-flag-configurations">Feature flag configurations</h3>
<p>Flipt will import all feature flag files under the folder <code>features</code> - during build docker copies this folder into the path <code>/data</code> 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:</p>
<ul>
<li><p><code>**/features.yaml</code></p>
</li>
<li><p><code>**/features.yml</code></p>
</li>
<li><p><code>**/*.features.yaml</code></p>
</li>
<li><p><code>**/*.features.yml</code></p>
</li>
</ul>
<p>Flipt uses namespaces to organize feature flags. I would recommend you to map these 1:1 to the files, so that namespace <code>alpha</code> has an appropriate file <code>alpha.features.yml</code>. 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 <code>alpha</code>, <code>beta</code>, <code>gamma</code>, then each would have a feature flag file <code>alpha.features.yml</code>, <code>beta.features.yml</code> and <code>gamma.features.yml</code>. As an example, you can add flags like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"1.2"</span>
<span class="hljs-attr">namespace:</span> <span class="hljs-string">alpha</span>
<span class="hljs-attr">flags:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">feature-xyz</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Feature</span> <span class="hljs-string">XYZ</span> <span class="hljs-string">is</span> <span class="hljs-string">in</span> <span class="hljs-string">testing</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">BOOLEAN_FLAG_TYPE</span>
    <span class="hljs-attr">enabled:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">rollouts:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">segment:</span>
          <span class="hljs-attr">key:</span> <span class="hljs-string">internal-users</span>
          <span class="hljs-attr">value:</span> <span class="hljs-literal">true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">threshold:</span>
          <span class="hljs-attr">percentage:</span> <span class="hljs-number">20</span>
          <span class="hljs-attr">value:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">segments:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">key:</span> <span class="hljs-string">internal-users</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Internal</span> <span class="hljs-string">Users</span>
    <span class="hljs-attr">match_type:</span> <span class="hljs-string">ANY_MATCH_TYPE</span>
    <span class="hljs-attr">constraints:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">property:</span> <span class="hljs-string">organization</span>
        <span class="hljs-attr">operator:</span> <span class="hljs-string">eq</span>
        <span class="hljs-attr">value:</span> <span class="hljs-string">internal</span>
        <span class="hljs-attr">type:</span> <span class="hljs-string">STRING_COMPARISON_TYPE</span>
</code></pre>
<p>With this, you have configured a new flag <code>feature-xyz</code> and a segment for internal users, where this feature is on by default. The segment <code>internal-users</code> is defined by a property which has to be equal to <code>internal</code>. When in an evaluation request the <code>organization</code> property equals <code>internal</code>, the response will be <code>true</code>. Otherwise, it will be <code>true</code> 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:</p>
<pre><code class="lang-http"><span class="hljs-attribute">POST http://localhost:8080/evaluate/v1/boolean
Content-Type</span>: application/json

<span class="json">{
  <span class="hljs-attr">"namespaceKey"</span>: <span class="hljs-string">"alpha"</span>,
  <span class="hljs-attr">"flagKey"</span>: <span class="hljs-string">"feature-xyz"</span>,
  <span class="hljs-attr">"entityId"</span>: <span class="hljs-string">"user-123"</span>,
  <span class="hljs-attr">"context"</span>: {
    <span class="hljs-attr">"organization"</span>: <span class="hljs-string">"internal"</span>
  }
}</span>
</code></pre>
<p>Notice that we have used the user ID as the <code>entityId</code>, meaning that the rollout distribution (threshold 20%) for requests outside the internal organization will be based on the unique users.</p>
<h1 id="heading-flipt-and-gitops">Flipt and GitOps</h1>
<p>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.</p>
<p>The workflow to change, add or remove feature flags is very simple:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744140044738/35b64d28-9b2a-43a6-bcbb-f23b4c730675.png" alt class="image--center mx-auto" /></p>
<p>First, a contributor commits changes to the <code>main</code> 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.)</p>
<p>For example, if team <code>alpha</code> want to create a feature flag <code>feature-new</code>, they open the file for the <code>alpha</code> team’s namespace and add it as a new flag:</p>
<pre><code class="lang-diff">diff --git i/src/features/alpha.features.yml w/src/features/alpha.features.yml
index c736e87..fa47c9b 100644
<span class="hljs-comment">--- i/src/features/alpha.features.yml</span>
<span class="hljs-comment">+++ w/src/features/alpha.features.yml</span>
@@ -12,6 +12,10 @@ flags:
       - threshold:
           percentage: 20
           value: true
<span class="hljs-addition">+  - key: feature-new</span>
<span class="hljs-addition">+    name: New feature</span>
<span class="hljs-addition">+    type: BOOLEAN_FLAG_TYPE</span>
<span class="hljs-addition">+    enabled: false</span>
 segments:
   - key: internal-users
     name: Internal Users
</code></pre>
<p>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.</p>
<h1 id="heading-enabling-your-users-to-configure-feature-toggles-themselves">Enabling your users to configure feature toggles themselves</h1>
<p>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.</p>
<p>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:</p>
<ol>
<li><p>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.</p>
</li>
<li><p>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.</p>
</li>
</ol>
<p>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 <em>Feature Manager.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744139268354/8fd84f3f-7c1c-495d-bbb7-ac05aefaaafb.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-required-apis-of-feature-manager">Required APIs of Feature Manager</h2>
<p>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 <em>same</em> 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.</p>
<p>The Feature Manager performs evaluation of a flag as follows:</p>
<ol>
<li><p>Call the evaluation API provided by a wrapper service and include in body: a batch of feature flags to resolve, tenant, user ID.</p>
</li>
<li><p>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.</p>
</li>
<li><p>Resolve the remaining features by calling the Flipt evaluation API.</p>
</li>
<li><p>Return the consolidated result.</p>
</li>
</ol>
<p>Second API we need is for configuring and managing feature toggles available to users. Let’s call it the <em>Feature Toggle Management API</em>. 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:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">openapi:</span> <span class="hljs-number">3.0</span><span class="hljs-number">.0</span>
<span class="hljs-attr">info:</span>
  <span class="hljs-attr">title:</span> <span class="hljs-string">Feature</span> <span class="hljs-string">Toggle</span> <span class="hljs-string">Management</span> <span class="hljs-string">API</span>
  <span class="hljs-attr">description:</span> <span class="hljs-string">API</span> <span class="hljs-string">for</span> <span class="hljs-string">configuring</span> <span class="hljs-string">and</span> <span class="hljs-string">managing</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggles</span> <span class="hljs-string">available</span> <span class="hljs-string">to</span> <span class="hljs-string">users.</span>
  <span class="hljs-attr">version:</span> <span class="hljs-string">"1.0.0"</span>
<span class="hljs-attr">servers:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">url:</span> <span class="hljs-string">https://tenant.app.com/api/v1</span>
<span class="hljs-attr">paths:</span>
  <span class="hljs-string">/feature-toggles:</span>
    <span class="hljs-attr">get:</span>
      <span class="hljs-attr">summary:</span> <span class="hljs-string">List</span> <span class="hljs-string">all</span> <span class="hljs-string">available</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggles</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">Returns</span> <span class="hljs-string">a</span> <span class="hljs-string">list</span> <span class="hljs-string">of</span> <span class="hljs-string">all</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggles</span> <span class="hljs-string">available</span> <span class="hljs-string">in</span> <span class="hljs-string">the</span> <span class="hljs-string">system.</span>
      <span class="hljs-attr">parameters:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">namespace</span>
          <span class="hljs-attr">in:</span> <span class="hljs-string">query</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Optional</span> <span class="hljs-string">namespace</span> <span class="hljs-string">for</span> <span class="hljs-string">scoping</span> <span class="hljs-string">toggles.</span>
          <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
          <span class="hljs-attr">schema:</span>
            <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
            <span class="hljs-attr">default:</span> <span class="hljs-string">default</span>
      <span class="hljs-attr">responses:</span>
        <span class="hljs-attr">'200':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">A</span> <span class="hljs-string">JSON</span> <span class="hljs-string">array</span> <span class="hljs-string">of</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggles.</span>
          <span class="hljs-attr">content:</span>
            <span class="hljs-attr">application/json:</span>
              <span class="hljs-attr">schema:</span>
                <span class="hljs-attr">type:</span> <span class="hljs-string">array</span>
                <span class="hljs-attr">items:</span>
                  <span class="hljs-string">$ref:</span> <span class="hljs-string">'#/components/schemas/FeatureToggle'</span>
  <span class="hljs-string">/feature-toggles/{toggleId}:</span>
    <span class="hljs-attr">parameters:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">toggleId</span>
        <span class="hljs-attr">in:</span> <span class="hljs-string">path</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">Unique</span> <span class="hljs-string">identifier</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle.</span>
        <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">schema:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">namespace</span>
        <span class="hljs-attr">in:</span> <span class="hljs-string">query</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">Optional</span> <span class="hljs-string">namespace</span> <span class="hljs-string">for</span> <span class="hljs-string">scoping</span> <span class="hljs-string">toggles.</span>
        <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
        <span class="hljs-attr">schema:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
          <span class="hljs-attr">default:</span> <span class="hljs-string">default</span>
    <span class="hljs-attr">get:</span>
      <span class="hljs-attr">summary:</span> <span class="hljs-string">Get</span> <span class="hljs-string">a</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle</span> <span class="hljs-string">by</span> <span class="hljs-string">ID</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">Retrieves</span> <span class="hljs-string">the</span> <span class="hljs-string">details</span> <span class="hljs-string">of</span> <span class="hljs-string">a</span> <span class="hljs-string">specific</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle.</span>
      <span class="hljs-attr">responses:</span>
        <span class="hljs-attr">'200':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Feature</span> <span class="hljs-string">toggle</span> <span class="hljs-string">details.</span>
          <span class="hljs-attr">content:</span>
            <span class="hljs-attr">application/json:</span>
              <span class="hljs-attr">schema:</span>
                <span class="hljs-string">$ref:</span> <span class="hljs-string">'#/components/schemas/FeatureToggle'</span>
        <span class="hljs-attr">'404':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Feature</span> <span class="hljs-string">toggle</span> <span class="hljs-string">not</span> <span class="hljs-string">found.</span>
    <span class="hljs-attr">put:</span>
      <span class="hljs-attr">summary:</span> <span class="hljs-string">Update</span> <span class="hljs-string">a</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">Updates</span> <span class="hljs-string">the</span> <span class="hljs-string">details</span> <span class="hljs-string">of</span> <span class="hljs-string">a</span> <span class="hljs-string">specific</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle.</span>
      <span class="hljs-attr">requestBody:</span>
        <span class="hljs-attr">required:</span> <span class="hljs-literal">true</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">Set</span> <span class="hljs-string">a</span> <span class="hljs-string">new</span> <span class="hljs-string">value</span> <span class="hljs-string">for</span> <span class="hljs-string">the</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle.</span>
        <span class="hljs-attr">content:</span>
          <span class="hljs-attr">application/json:</span>
            <span class="hljs-attr">schema:</span>
              <span class="hljs-string">$ref:</span> <span class="hljs-string">'#/components/schemas/FeatureToggleUpdate'</span>
      <span class="hljs-attr">responses:</span>
        <span class="hljs-attr">'200':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Feature</span> <span class="hljs-string">toggle</span> <span class="hljs-string">updated</span> <span class="hljs-string">successfully.</span>
          <span class="hljs-attr">content:</span>
            <span class="hljs-attr">application/json:</span>
              <span class="hljs-attr">schema:</span>
                <span class="hljs-string">$ref:</span> <span class="hljs-string">'#/components/schemas/FeatureToggle'</span>
        <span class="hljs-attr">'400':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Invalid</span> <span class="hljs-string">input.</span>
        <span class="hljs-attr">'404':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Feature</span> <span class="hljs-string">toggle</span> <span class="hljs-string">not</span> <span class="hljs-string">found.</span>
    <span class="hljs-attr">delete:</span>
      <span class="hljs-attr">summary:</span> <span class="hljs-string">Delete</span> <span class="hljs-string">a</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle</span>
      <span class="hljs-attr">description:</span> <span class="hljs-string">Deletes</span> <span class="hljs-string">a</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle</span> <span class="hljs-string">configuration</span> <span class="hljs-string">from</span> <span class="hljs-string">this</span> <span class="hljs-string">tenant.</span>
      <span class="hljs-attr">responses:</span>
        <span class="hljs-attr">'204':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Feature</span> <span class="hljs-string">toggle</span> <span class="hljs-string">configuration</span> <span class="hljs-string">deleted</span> <span class="hljs-string">successfully.</span>
        <span class="hljs-attr">'404':</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Feature</span> <span class="hljs-string">toggle</span> <span class="hljs-string">not</span> <span class="hljs-string">found.</span>
<span class="hljs-attr">components:</span>
  <span class="hljs-attr">schemas:</span>
    <span class="hljs-attr">FeatureToggle:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">object</span>
      <span class="hljs-attr">properties:</span>
        <span class="hljs-attr">id:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Unique</span> <span class="hljs-string">identifier</span> <span class="hljs-string">for</span> <span class="hljs-string">the</span> <span class="hljs-string">toggle.</span>
        <span class="hljs-attr">name:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Name</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle.</span>
        <span class="hljs-attr">enabled:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">boolean</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Indicates</span> <span class="hljs-string">whether</span> <span class="hljs-string">the</span> <span class="hljs-string">feature</span> <span class="hljs-string">is</span> <span class="hljs-string">enabled.</span> <span class="hljs-literal">Null</span> <span class="hljs-string">if</span> <span class="hljs-string">not</span> <span class="hljs-string">configured.</span>
        <span class="hljs-attr">description:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">string</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">A</span> <span class="hljs-string">brief</span> <span class="hljs-string">description</span> <span class="hljs-string">of</span> <span class="hljs-string">what</span> <span class="hljs-string">the</span> <span class="hljs-string">toggle</span> <span class="hljs-string">controls.</span>
      <span class="hljs-attr">required:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">id</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">name</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">enabled</span>
    <span class="hljs-attr">FeatureToggleUpdate:</span>
      <span class="hljs-attr">type:</span> <span class="hljs-string">object</span>
      <span class="hljs-attr">properties:</span>
        <span class="hljs-attr">enabled:</span>
          <span class="hljs-attr">type:</span> <span class="hljs-string">boolean</span>
          <span class="hljs-attr">description:</span> <span class="hljs-string">Updated</span> <span class="hljs-string">state</span> <span class="hljs-string">of</span> <span class="hljs-string">the</span> <span class="hljs-string">feature</span> <span class="hljs-string">toggle.</span>
      <span class="hljs-attr">required:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">enabled</span>
</code></pre>
<h1 id="heading-summary">Summary</h1>
<p>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.</p>
<p>In <em>Part 2</em>, 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.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://www.flipt.io">https://www.flipt.io</a>, <a target="_blank" href="https://docs.flipt.io/usecases/gitops">https://docs.flipt.io</a></p>
</li>
<li><p><a target="_blank" href="https://openfeature.dev">https://openfeature.dev</a></p>
</li>
<li><p><a target="_blank" href="https://launchdarkly.com/blog/is-it-a-feature-flag-or-a-feature-toggle/">https://launchdarkly.com/blog/is-it-a-feature-flag-or-a-feature-toggle</a></p>
</li>
</ul>
]]></content:encoded></item></channel></rss>