Skip to content

Getting started

This is the hands-on platform path: you set up Airflow, enable the policy, and ramp a flag. If you only want to gate or A/B a code path inside a DAG you write, see Use cases instead.

By the end of this walkthrough you will:

  • install the provider,
  • point Airflow at a local flagd feature-flag daemon,
  • watch a flag move a DAG's task onto a canary pool,
  • ramp that flag from a couple of DAGs to all of them,
  • flip a kill switch and watch every DAG revert.

No experimentation-platform account needed. Everything runs locally against the open-source flagd daemon. About five minutes.

0. Prerequisites

  • Python 3.10+ and pip
  • Docker (to run flagd)

1. Install

pip install "airflow-provider-openfeature[flagd]"

Installing changes nothing about your Airflow until you opt in (steps 3). The policy and listener stay off by default.

2. Start flagd with one flag

Create a flag file. It routes two DAGs (etl_alpha, etl_beta) to a canary_pool; everything else stays on the default.

mkdir -p flags && cat > flags/flags.json <<'JSON'
{
  "flags": {
    "airflow.task.pool": {
      "state": "ENABLED",
      "variants": { "canary": "canary_pool", "default": "default_pool" },
      "defaultVariant": "default",
      "targeting": { "if": [ { "in": [ {"var": "dag_id"}, ["etl_alpha", "etl_beta"] ] }, "canary", "default" ] }
    }
  }
}
JSON

docker run -d --name flagd -p 8013:8013 -v "$PWD/flags:/flags" \
  ghcr.io/open-feature/flagd:latest start --uri file:/flags/flags.json

flagd watches that file and hot-reloads on change. That's what makes the ramp in step 6 instant.

3. Point Airflow at flagd, and turn the policy on

Two things: register flagd as the OpenFeature backend, and enable the placement policy.

Register the backend in $AIRFLOW_HOME/config/airflow_local_settings.py:

import time
from openfeature import api
from openfeature.contrib.provider.flagd import FlagdProvider

api.set_provider(FlagdProvider(host="localhost", port=8013))
time.sleep(1)  # the gRPC resolver needs a moment to connect

Enable the policy (it ships off; this is the opt-in):

export AIRFLOW__OPENFEATURE__ENABLE_POLICY=True

You do not write a task_policy function. The airflow.policy entry point registers it for you; the config flag turns it on.

4. Add a DAG

# $AIRFLOW_HOME/dags/etl_alpha.py
from airflow import DAG
try:
    from airflow.providers.standard.operators.empty import EmptyOperator  # Airflow 3.x
except ImportError:
    from airflow.operators.empty import EmptyOperator  # Airflow 2.x
import datetime

with DAG(dag_id="etl_alpha", schedule=None, start_date=datetime.datetime(2024, 1, 1), catchup=False) as dag:
    EmptyOperator(task_id="run", pool="default_pool")

The task asks for default_pool. The flag will override it.

5. Run it, and read the placement back

airflow dags test etl_alpha

The task ran in canary_pool, not the default_pool the DAG asked for. flagd put etl_alpha in the canary subset; the policy applied it. Confirm from the metadata DB:

airflow tasks states-for-dag-run etl_alpha <run_id>   # pool column shows canary_pool

A DAG that isn't in the subset (say etl_gamma) runs in default_pool. Same code, different placement.

6. Ramp it

Widen the subset in the flag file. flagd hot-reloads; no restart, no redeploy.

# add more DAGs to the "in" list, or switch to a percentage with flagd's fractional operator:
#   "targeting": { "fractional": [ ["canary", 25], ["default", 75] ] }

Re-run any DAG and more of them land on canary_pool. The assignment is deterministic, so a DAG that was canary at 25% stays canary at 50%.

7. Kill switch

Something looks wrong. Empty the subset:

"targeting": { "if": [ { "in": [ {"var": "dag_id"}, [] ] }, "canary", "default" ] }

flagd reloads, and the next run of every DAG is back on default_pool. One flag change, no code edit, no deploy.

What just happened

The policy runs at DAG-parse time. For each task it asks OpenFeature to resolve airflow.task.pool, keyed on the task's dag_id:task_id, and applies whatever comes back. flagd decided who was in the subset. Swap flagd for GrowthBook, Unleash, Statsig, or an in-house engine and nothing in your Airflow changes; the backend just answers the same question.

The policy reads three more well-known flags the same way: airflow.task.queue, airflow.task.executor, and airflow.task.priority_weight. Set any of them to route a subset's queue, executor, or priority.

Next

  • Use cases: 2→3 migration, KubernetesExecutor rollout, A/B testing, kill switch, cost routing.
  • Examples: runnable DAGs for each pattern.
  • Register a backend: GrowthBook, Unleash, Statsig, PostHog, or your own.
  • Clean up: docker rm -f flagd.