Skip to main content

Overview

A release is the set of decisions about when and how a change becomes visible to users, independent of when its code reaches the production environment. The core principle is separating deploy from release: deploying is the mechanical act of shipping code, releasing is the decision to expose a feature. This page explains that separation, the feature flag mechanism that implements it, how it enables trunk-based development, and how to roll out, roll back, and clean up safely. The pipeline that automates deploys themselves is covered on the CI/CD page; this page covers deciding when users see the result.

Deploy and release are different events

Deploying code and releasing a feature usually happen at the same moment, but they answer different questions and belong to different owners.

Deploy

Reflecting code in the production environment. A mechanical, repeatable step that can be automated and performed many times a day.

Release

Making a feature available to users. A judgment call about timing — coordination with announcements, support readiness, or business milestones.
Keeping the two coupled forces a trade-off: either deploys wait for the business timing, so changes pile up into large risky batches, or features go live the instant their code lands, whether or not anyone is ready. Separating them is the foundation of continuous delivery — keeping the software releasable at all times, deploying small changes frequently, and choosing release timing deliberately. Small, frequent deploys improve speed and stability together, because each change is easier to review, test, and undo.

Feature Flags

A feature flag is a conditional that switches behavior on or off without a code change. The simplest reliable implementation is an environment variable read at one branch point in the code. In the following example, a new label is rendered only while the FEATURE_NEW_LABEL environment variable is 'true'. Enable it locally to work on the feature, and merge with it disabled in the production environment.
Enabling the feature in the production environment is a configuration change — add FEATURE_NEW_LABEL=true to the environment — and disabling it is removing that line. The flag turns “the code is in the production environment” and “the feature is visible” into independent facts. That unlocks:

Merge unfinished work

In-progress features merge into the main branch behind a flag, invisible to users, instead of aging in a long-lived branch.

Deploy often, release deliberately

Deploy to the production environment many times a day while choosing when to release each feature.

Instant rollback

If a released feature misbehaves, turn the flag off. Recovery time is minimal.

Test safely in the production environment

Enable a feature in the verification environment or for internal use first, while users in the production environment still see the old behavior.
Environment-variable flags can be implemented with no extra infrastructure, which makes them a good first step. Adopting a flag management library or service is also worth considering.

Trunk-based development

Trunk-based development is the practice of integrating all work into the main branch in small increments, avoiding branches that live for days or weeks. Long-lived branches accumulate merge conflicts, hide integration problems until the end, and produce large pull requests that are hard to review. Feature flags are what make this practical for features that take more than one pull request to build: each increment merges as soon as it passes review, guarded by the flag, and the feature is enabled only when the last piece lands. The result is that several people can build in the same area with conflicts less likely, and every merge is small enough to review properly. In the following diagram, one feature is split into three increments, each merged into the base branch with the flag off, and the release happens by enabling the flag after the last increment lands. This is the same principle as splitting work into small units — see Task Breakdown for how to slice the work and Pull Requests for keeping each merge reviewable.

Isolate impact on the production environment with a topic branch flow

Some changes cannot hide behind a flag — a visual overhaul or a framework migration, where the impact on the production environment cannot be hidden. The topic branch flow keeps granularity in that case. It is a three-layer branch structure that places a topic branch between the base branch and the working branches. Review and integration complete on the topic branch, and the base branch receives nothing until release, so small pull requests keep flowing without affecting the production environment. The flow works as follows.
  1. Cut a topic branch from the base branch, then cut working develop branches from the topic branch.
  2. Review on PRs from a develop branch into the topic branch. This is the stage where granularity is maintained.
  3. When the work is ready for the production environment, merge a PR from the topic branch into the base branch. The review at this point can be minimal, because the changes were already reviewed in step 2.
  4. Merge the base branch’s changes into the topic branch at least once a day to keep conflicts to a minimum.
The trade-off is that the topic branch itself is a long-lived branch: integration with the base branch is deferred, which is exactly what trunk-based development avoids, so the daily sync in step 4 is what keeps conflicts manageable. Prefer feature flags when the change can hide behind one, and reserve the topic branch flow for changes that cannot.

Gradual rollout and rollback

With deploy and release separated, releasing becomes a sequence of controlled steps rather than a single event.
1

Deploy with the flag off

The code reaches the production environment disabled. Users see no change; the deploy itself carries near-zero release risk.
2

Enable in the verification environment

Turn the flag on in the verification environment first. Problems surface before any user is exposed — the earlier a defect is found, the cheaper it is to fix.
3

Release in the production environment

Enable the flag in the production environment at the chosen timing. If the rollout can be scoped, start with a specific group of users and widen the audience gradually.
4

Roll back by turning the flag off

If the feature misbehaves, disable the flag.
Rollback by flag only works if the old code path still exists and still passes its tests. Do not delete or break the fallback path while the flag is live.

Test both states, then remove the flag

A flag doubles the states your code can be in, and both states run in the production environment at some point in the feature’s life. Write tests for each side of the branch:
The off-state test is not optional: it is what guarantees the rollback path keeps working while the feature evolves. See Test Code for what makes these tests trustworthy. A flag is temporary by design. Once the feature has run stably with the flag on and there is no scenario for turning it back off, remove the flag, the dead branch, and the off-state tests in one pull request. Stale flags accumulate into a combinatorial maze of states nobody tests. A trustworthy test suite is what makes this deletion safe to do promptly.

Task Breakdown

Slice features into increments small enough to merge behind a flag.

Pull Requests

Keep each merge small and reviewable.

Test Code

Tests that protect both flag states and make flag removal safe.