• Data & Analytics

Apache Airflow Guide

Published On: 18 November 2025.By .
Apache Airflow · Data Pipelines · Workflow Orchestration

If you have ever had a data pipeline fail silently at 3 AM, a scheduled job run in the wrong order, or a team spend hours manually triggering tasks that should have run automatically — you have already felt the problem Apache Airflow was built to solve.

Airflow is the de facto standard for workflow orchestration. Over 80,000 organisations run it. It sees more than 30 million downloads every month. And in April 2025, it released its most significant update since version 2.0 in 2020.

This guide covers Apache Airflow 3.x — the latest major version — from installation through production-grade pipelines, updated for 2026.

Latest: Airflow 3.2.2 — May 2026 Airflow 2.x EOL — April 2026 Requires Python 3.9+

What is Apache Airflow?

Apache Airflow is an open-source platform for programmatically authoring, scheduling, and monitoring workflows. Originally developed at Airbnb in 2014, it allows you to define workflows as Python code — represented as Directed Acyclic Graphs (DAGs), where nodes are tasks and edges define the order of execution.

Data engineers use it for ETL pipelines. Machine learning engineers use it for model training and inference workflows. DevOps teams use it for infrastructure automation.

30M+ Monthly Downloads
80K+ Organisations Using Airflow
3.2.2 Latest Stable Version
30%+ Users Running MLOps

What Changed in Airflow 3.x

Airflow 3.0 was released in April 2025. This is not a collection of feature additions — it changes the UI, the security model, how tasks communicate with the scheduler, and how DAG versions are tracked.

Important: Airflow 2.x reaches end-of-life in April 2026. If you are still on 2.x, migration planning should be underway now.

New UI

Rebuilt in React

The old Flask/Jinja UI is gone. The new React frontend loads faster, handles thousands of DAGs, and adds DAG pinning, 17 UI languages, and redesigned Grid and Gantt views.

DAG Versioning

Reproducible Runs

Every DAG run is tied to the exact version of code that triggered it. Updating a DAG no longer disrupts jobs already in flight.

Assets

Event-Driven Scheduling

Datasets from 2.x are renamed Assets. DAGs can now trigger automatically when an asset is updated — no workarounds needed.

Security

Task Execution API

Tasks no longer access the metadata database directly. All state transitions go through a dedicated API — preventing buggy code from corrupting your installation.

AI / ML

Native Workflow Support

Model training, inference pipelines, and GenAI workflows are now first-class citizens — not bolted-on afterthoughts.

SDK Imports

Unified airflow.sdk

DAG authoring now uses a single SDK surface. Imports that previously required scattered module paths are consolidated into one clean entry point.

Core Concepts

DAGs

Directed Acyclic Graphs

A DAG is your entire workflow — schedule, retry policy, task order, and dependencies. In Airflow 3.x, every run references the exact code version that triggered it.

Tasks and Operators

Units of Work

Tasks are individual work units created using Operators. Common ones include PythonOperator, BashOperator, EmailOperator, and Sensors for waiting on external events.

Assets

Data Dependencies

Assets represent data that DAGs produce or consume. When an upstream DAG updates an asset, downstream DAGs trigger automatically — no polling required.

Executors

How Tasks Run

SequentialExecutor for testing. LocalExecutor for single-machine parallelism. CeleryExecutor and KubernetesExecutor for distributed production workloads.

Installing Apache Airflow 3.x

Always install using the official constraint file. Without it, pip may resolve incompatible dependency versions that break Airflow silently.

Bash — Installation
# Set Airflow home
export AIRFLOW_HOME=~/airflow

AIRFLOW_VERSION=3.2.2
PYTHON_VERSION="$(python --version | cut -d ' ' -f 2 | cut -d '.' -f 1-2)"
CONSTRAINT_URL="https://raw.githubusercontent.com/apache/airflow/constraints-${AIRFLOW_VERSION}/constraints-${PYTHON_VERSION}.txt"

pip install "apache-airflow==${AIRFLOW_VERSION}" \
    --constraint "${CONSTRAINT_URL}"

# Initialise the database
airflow db migrate

# Create admin user
airflow users create \
    --username admin \
    --firstname Admin \
    --lastname User \
    --role Admin \
    --email admin@example.com

# Start services in separate terminals
airflow webserver --port 8080
airflow scheduler

Airflow 3.x change: airflow db init is replaced by airflow db migrate. The new React UI is at http://localhost:8080.

Your First DAG in Airflow 3.x

Create a Python file in your DAGs folder. Airflow 3.x uses SDK-first imports and a cleaner context manager syntax.

Python — hello_airflow.py
from datetime import datetime, timedelta
from airflow.sdk import DAG  # New unified import in 3.x
from airflow.operators.bash import BashOperator
from airflow.operators.python import PythonOperator

default_args = {
    'owner': 'data-team',
    'retries': 2,
    'retry_delay': timedelta(minutes=5),
}

with DAG(
    dag_id='hello_airflow',
    default_args=default_args,
    schedule=timedelta(days=1),  # schedule_interval deprecated in 3.x
    start_date=datetime(2026, 1, 1),
    catchup=False,
    tags=['tutorial'],
) as dag:

    print_date = BashOperator(
        task_id='print_date',
        bash_command='date',
    )

    def greet():
        print("Hello from Airflow 3.x!")

    greet_task = PythonOperator(
        task_id='greet_user',
        python_callable=greet,
    )

    print_date >> greet_task

The TaskFlow API — using @dag and @task decorators — is the recommended approach in Airflow 3.x. It handles XCom automatically and produces much cleaner code.

Python — TaskFlow API (recommended)
from datetime import datetime
from airflow.sdk import dag, task

@dag(schedule="@daily", start_date=datetime(2026, 1, 1), catchup=False)
def my_pipeline():

    @task
    def extract():
        return {"records": 100, "status": "ok"}

    @task
    def transform(data: dict):
        data["processed"] = True
        return data

    @task
    def load(data: dict):
        print(f"Loading {data['records']} records")

    load(transform(extract()))

my_pipeline()

Managing Task Dependencies

Airflow uses Python's bitshift operators to define execution order — readable, expressive, and handles fan-out and fan-in cleanly.

Python — Dependency patterns
# Linear: A then B then C
task_a >> task_b >> task_c

# Fan-out: A triggers B, C, D simultaneously
task_a >> [task_b, task_c, task_d]

# Fan-in: B, C, D must complete before E
[task_b, task_c, task_d] >> task_e

# Complex chains
start >> [parallel_1, parallel_2] >> combine >> end

Building a Production ETL Pipeline

A complete production-ready pipeline using the TaskFlow API. Data passes between tasks naturally — no manual XCom boilerplate required.

Python — etl_pipeline.py
from datetime import datetime, timedelta
from airflow.sdk import dag, task
import pandas as pd, requests, json

@dag(
    schedule='0 3 * * *',
    start_date=datetime(2026, 1, 1),
    catchup=False,
    default_args={
        'owner': 'data-engineering',
        'retries': 3,
        'retry_delay': timedelta(minutes=5),
    },
)
def etl_pipeline():

    @task
    def extract(**context):
        url = f'https://api.example.com/data?date={context["ds"]}'
        data = requests.get(url, timeout=30).json()
        path = f'/tmp/raw_{context["ds"]}.json'
        with open(path, 'w') as f:
            json.dump(data, f)
        return path

    @task
    def transform(file_path: str, **context):
        df = pd.read_json(file_path)
        df['processed_date'] = pd.to_datetime(context['ds'])
        df['value_doubled'] = df['value'] * 2
        out = f'/tmp/transformed_{context["ds"]}.csv'
        df.to_csv(out, index=False)
        return out

    @task
    def load(file_path: str):
        import sqlite3
        df = pd.read_csv(file_path)
        conn = sqlite3.connect('/tmp/warehouse.db')
        df.to_sql('analytics', conn, if_exists='append', index=False)
        conn.close()

    load(transform(extract()))

etl_pipeline()

Advanced Patterns

Dynamic Task Mapping

Python — Dynamic mapping
@task
def process_partition(partition_id: str):
    print(f"Processing {partition_id}")

# Tasks created dynamically at runtime
process_partition.expand(
    partition_id=['north', 'south', 'east', 'west']
)

Asset-Driven Scheduling (new in 3.x)

Python — Event-driven with Assets
from airflow.sdk import Asset

orders_asset = Asset("s3://my-bucket/orders/")

# Producer — publishes the asset
@dag(schedule="@daily")
def produce_orders():
    @task(outlets=[orders_asset])
    def write_orders():
        pass
    write_orders()

# Consumer — triggers when asset updates
@dag(schedule=[orders_asset])
def consume_orders():
    @task
    def process():
        print("New orders — processing now")
    process()

Airflow 2.x vs 3.x — Key Differences

Feature Airflow 2.x Airflow 3.x
ImportsMultiple scattered pathsairflow.sdk
Schedule paramschedule_intervalschedule
DB init commandairflow db initairflow db migrate
UI frameworkFlask / JinjaReact
DAG versioningNot availableBuilt-in
Event schedulingDatasets (limited)Assets (full support)
Task DB accessDirect (security risk)Via Task Execution API
AI/ML workflowsVia plugins onlyNative
End of lifeApril 2026Active development

Best Practices for Production

Design for Idempotency

Every task should produce the same result whether it runs once or ten times. Delete before inserting — never append blindly.

Python — Idempotent load
@task
def idempotent_load(**context):
    import sqlite3
    date = context['ds']
    conn = sqlite3.connect('/tmp/db.db')
    conn.execute(
        "DELETE FROM metrics WHERE date = ?", (date,)
    )
    df = pd.read_csv(f'/tmp/data_{date}.csv')
    df.to_sql('metrics', conn, if_exists='append', index=False)
    conn.commit()
    conn.close()

Never Hardcode Credentials

Python — Secure variable retrieval
from airflow.models import Variable

# Store via CLI: airflow variables set api_key "value"
api_key = Variable.get("api_key")

# Configure DB connections via Admin > Connections in the UI
# Never hardcode credentials directly in DAG files

Essential CLI Commands

Bash — CLI reference
airflow dags list
airflow dags trigger etl_pipeline
airflow tasks test etl_pipeline extract 2026-01-15
airflow dags pause etl_pipeline
airflow dags unpause etl_pipeline
airflow dags backfill etl_pipeline -s 2026-01-01 -e 2026-01-31

Frequently Asked Questions

What is the latest version of Apache Airflow?

The latest stable version is Apache Airflow 3.2.2, released on 29 May 2026. The 3.x line is the current active major version and includes a new React UI, DAG versioning, asset-driven scheduling, and native AI and ML workflow support.

Should I upgrade from Airflow 2.x to 3.x?

Yes. Airflow 2.x reaches end-of-life in April 2026. If you need event-driven scheduling, DAG versioning, improved security, or a modern UI, migrate now. Test thoroughly on a staging environment before cutting over to production.

What replaced Datasets in Airflow 3.x?

Datasets from Airflow 2.x are renamed to Assets in Airflow 3.x. Assets support richer event-driven scheduling — downstream DAGs trigger automatically when an upstream DAG produces or updates an asset, without polling or workarounds.

What is the TaskFlow API in Apache Airflow?

The TaskFlow API lets you write DAGs using Python decorators — @dag and @task — instead of manually instantiating Operator classes. It handles XCom data passing automatically between tasks and produces significantly cleaner, more readable code. It is the recommended approach in Airflow 3.x.

What is the difference between schedule and schedule_interval?

schedule_interval was the parameter used in Airflow 2.x. In Airflow 3.x, it is replaced by schedule. The new parameter accepts cron expressions, timedelta objects, preset strings like @daily, or a list of Assets for event-driven triggering.

What does airflow db migrate do?

airflow db migrate is the Airflow 3.x replacement for airflow db init. It initialises or upgrades the Airflow metadata database schema. Run it after installing Airflow for the first time, and again whenever you upgrade to a new version.

Can I run Airflow 3.x with Python 3.8?

No. Airflow 3.x requires Python 3.9 or higher. Python version support has been modernised in the 3.x line, dropping older versions to reduce the dependency surface and improve stability.

Need Help Implementing Airflow in Your Organisation?

Auriga IT helps engineering teams architect, deploy, and scale data pipelines using Apache Airflow and modern data engineering tools.

Whether you are starting fresh or migrating from 2.x, we can help you get it right.

Talk to Our Team

Related content

Stay Close to What We’re Building

Get insights on product engineering, AI, and real-world technology decisions shaping modern businesses.

Yashwardhan Gaur
Yashwardhan Gaur
Yashwardhan Gaur is an SDE-2 at Auriga IT specialising in backend development with Python, Django, and Django REST Framework. He focuses on building scalable, efficient applications and holds certifications in Django and full-stack web development.
Go to Top