Apache Airflow: DAG Orchestration, Setup & Best Practices
Apache Airflow is the industry-standard open-source platform for authoring, scheduling, and monitoring data pipelines as code. Define your workflows as Python DAGs and get full visibility into every task run, retry, and dependency.
What is Apache Airflow?
Apache Airflow is a platform to programmatically author, schedule, and monitor workflows. Originally created at Airbnb in 2014 and donated to the Apache Software Foundation in 2016, it has become the dominant open-source workflow orchestration tool for data engineering teams worldwide. Airflow represents pipelines as Directed Acyclic Graphs (DAGs) written in Python, which means pipelines can be dynamically generated, version-controlled in Git, and tested like any other code.
The Airflow scheduler continuously monitors all DAGs, triggers task instances when their dependencies are met, and distributes work to an executor. The web UI provides a graphical view of DAG runs, task logs, dependency graphs, and Gantt charts, making it straightforward to diagnose failures and monitor pipeline health in production.
Core Concepts
- DAG — a Python file defining tasks and their dependencies as a directed graph
- Operator — a template for a task: BashOperator, PythonOperator, SqlOperator, S3ToRedshiftOperator, etc.
- Sensor — a special operator that polls for a condition (file exists, API responds, S3 key present) before proceeding
- Task Instance — a specific run of an operator within a DAG run, with states: queued → running → success/failed
- XCom — mechanism for passing small values between tasks via the metadata database
- Connection — stored credentials for external systems (DB, API, cloud), referenced by conn_id
- Variable — key-value store for DAG configuration, optionally JSON-serialized
- Executor — the mechanism for running tasks: Local, Celery, or Kubernetes
DAG Definition with Operators & Sensors
# dags/etl_pipeline.py
from datetime import datetime, timedelta
from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.operators.bash import BashOperator
from airflow.providers.http.sensors.http import HttpSensor
default_args = {
'owner': 'data-team',
'retries': 2,
'retry_delay': timedelta(minutes=5),
'email_on_failure': True,
}
def extract_data(**context):
import requests
response = requests.get('https://api.example.com/data')
context['ti'].xcom_push(key='raw_data', value=response.json())
def transform_data(**context):
raw = context['ti'].xcom_pull(key='raw_data', task_ids='extract')
transformed = [{'id': r['id'], 'value': r['v'] * 2} for r in raw]
context['ti'].xcom_push(key='transformed', value=transformed)
with DAG(
dag_id='etl_pipeline',
default_args=default_args,
schedule_interval='@daily',
start_date=datetime(2024, 1, 1),
catchup=False,
tags=['etl', 'production'],
) as dag:
check_api = HttpSensor(
task_id='check_api',
http_conn_id='api_default',
endpoint='/health',
timeout=20,
poke_interval=5,
)
extract = PythonOperator(
task_id='extract',
python_callable=extract_data,
)
transform = PythonOperator(
task_id='transform',
python_callable=transform_data,
)
load = BashOperator(
task_id='load',
bash_command='python /opt/scripts/load_to_warehouse.py',
)
check_api >> extract >> transform >> load Docker Compose Quickstart
# docker-compose.yml (Airflow quickstart)
version: '3.8'
x-airflow-common: &airflow-common
image: apache/airflow:2.9.0
environment:
AIRFLOW__CORE__EXECUTOR: LocalExecutor
AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow
AIRFLOW__CORE__FERNET_KEY: ''
AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true'
AIRFLOW__API__AUTH_BACKENDS: airflow.api.auth.backend.basic_auth
volumes:
- ./dags:/opt/airflow/dags
- ./logs:/opt/airflow/logs
- ./plugins:/opt/airflow/plugins
depends_on:
postgres:
condition: service_healthy
services:
postgres:
image: postgres:15
environment:
POSTGRES_USER: airflow
POSTGRES_PASSWORD: airflow
POSTGRES_DB: airflow
healthcheck:
test: ["CMD", "pg_isready", "-U", "airflow"]
interval: 5s
airflow-webserver:
<<: *airflow-common
command: webserver
ports:
- "8080:8080"
airflow-scheduler:
<<: *airflow-common
command: scheduler
airflow-init:
<<: *airflow-common
command: version
entrypoint: /bin/bash
environment:
_AIRFLOW_DB_UPGRADE: 'true'
_AIRFLOW_WWW_USER_CREATE: 'true'
_AIRFLOW_WWW_USER_USERNAME: admin
_AIRFLOW_WWW_USER_PASSWORD: admin XComs, Variables & Connections
# XComs and Connections
from airflow.models import Variable
from airflow.hooks.base import BaseHook
# Read Airflow Variable (encrypted in metastore)
api_key = Variable.get('my_api_key', deserialize_json=False)
# Read a Connection
conn = BaseHook.get_connection('my_postgres')
print(conn.host, conn.schema, conn.login)
# Push/pull XCom values between tasks
def producer(**context):
context['ti'].xcom_push(key='result', value=42)
def consumer(**context):
val = context['ti'].xcom_pull(
task_ids='producer_task',
key='result'
)
print(f"Got: {val}") # 42 Airflow vs Prefect vs Dagster
| Feature | Apache Airflow | Prefect | Dagster |
|---|---|---|---|
| Paradigm | Task DAG (push) | Flow/Task (push) | Asset-based (pull) |
| Language | Python | Python | Python |
| UI | Webserver (self-hosted) | Prefect Cloud / self-hosted | Dagit (self-hosted) |
| Dynamic workflows | Limited (requires workarounds) | Native | Native (partitions) |
| Data lineage | Plugin required | Limited | First-class (Assets) |
| Learning curve | Medium | Low–Medium | Medium–High |
| Managed cloud | Astronomer / MWAA / Cloud Composer | Prefect Cloud | Dagster Cloud |
| Best for | Complex ETL, large teams | Modern Python teams | Data platform / lineage |
Best Practices
- Set
catchup=Falseon all production DAGs to prevent unintended backfills on re-deployment - Keep tasks atomic and idempotent — each task should do one thing and be safely re-runnable
- Never import heavy libraries at DAG file top level — Airflow parses all DAG files frequently; slow imports degrade scheduler performance
- Use XComs only for small values (IDs, counts, paths) — write large datasets to S3/GCS and pass the path
- Store all credentials in Connections or Variables, never hardcoded in DAG files
- Tag DAGs with
tags=[]and use consistent naming conventions for easy filtering in the UI
Frequently Asked Questions
What is a DAG in Apache Airflow?▼
A DAG (Directed Acyclic Graph) is a Python file that defines a workflow as a set of tasks and their dependencies. 'Directed' means data flows in one direction, 'Acyclic' means no circular dependencies. Each task in a DAG is an instance of an Operator (BashOperator, PythonOperator, etc.). Airflow's scheduler reads DAG files from the dags/ directory and schedules task runs based on the defined schedule_interval and start_date.
What is the difference between Airflow Executors?▼
The SequentialExecutor runs one task at a time (only for development). The LocalExecutor uses multiprocessing to run tasks in parallel on the same machine — suitable for small to medium workloads. The CeleryExecutor distributes tasks across a cluster of worker nodes using a message broker (Redis or RabbitMQ) — suited for large-scale production. The KubernetesExecutor spins up a fresh Pod per task in Kubernetes — provides strong isolation and resource control. Most production deployments use Celery or Kubernetes executors.
How do XComs work in Airflow?▼
XComs (Cross-Communications) allow tasks to exchange small pieces of data via Airflow's metadata database. A task pushes a value with ti.xcom_push(key, value) and a downstream task pulls it with ti.xcom_pull(task_ids, key). XComs are stored in the database so they are size-limited — keep them small (IDs, filenames, counts). For large datasets, write to an intermediate storage (S3, GCS, local disk) and pass only the path via XCom. In Airflow 2.x, PythonOperator automatically pushes the function return value as an XCom.
What is the catchup parameter in a DAG?▼
When catchup=True (the default), Airflow will backfill all DAG runs from the start_date to the current date when the DAG is first enabled. For a daily DAG with a start_date six months ago, this triggers 180 immediate runs. Setting catchup=False prevents backfilling — only the next scheduled run will execute. Unless you need historical backfills, always set catchup=False in production DAGs to avoid overwhelming your infrastructure when deploying or re-enabling a paused DAG.
How do Airflow Connections differ from Variables?▼
Connections store credentials for external systems (databases, APIs, cloud services) — they include fields for host, port, schema, login, password, and extra JSON config. They are managed via the Airflow UI under Admin > Connections or via environment variables (AIRFLOW_CONN_MY_CONN=...). Variables store arbitrary key-value configuration, optionally serialized as JSON. Variables are accessible anywhere in a DAG with Variable.get(). Both can be encrypted at rest using Fernet encryption configured via AIRFLOW__CORE__FERNET_KEY.
When should I use Airflow vs a simpler cron-based approach?▼
Use cron for simple, independent jobs with no inter-task dependencies and no need for retries, monitoring, or backfilling. Use Airflow when you have multi-step pipelines where later tasks depend on earlier ones, need automatic retries on failure, require centralized scheduling and visibility across many pipelines, need backfilling capability, or work across a team where visibility and auditability matter. The operational overhead of running Airflow (metadata database, scheduler, webserver) is not justified for one or two simple scripts.
Related Products
on GumroadDeveloper Productivity Bundle
Stop rebuilding the same setup every project. 51 VSCode snippets, 40 Git aliases, 5 Docker Compose stacks, 5 GitHub Actions workflows, 5 README templates, and a dotfiles kit. Set up a professional dev environment in under an hour.
Buy on Gumroad →AI Prompt Engineering Toolkit
58 structured prompt templates for Claude, ChatGPT, and Gemini — organized by use case. Code Review, Debugging, Documentation, Architecture, and 10 model-specific tips. Stop writing prompts from scratch.
Buy on Gumroad →Developer Cheat Sheet Bundle
10 print-ready cheat sheets for the tools developers use every day: Git, Docker, Linux CLI, SQL, Regex, HTTP Status codes, VS Code shortcuts, Bash scripting, jq/JSON, and Markdown.
Buy on Gumroad →You Might Also Need
Docker Compose Generator
Generate docker-compose.yml configurations visually in your browser. Add services, ports, volumes, environment variables, and networks. Export production-ready YAML instantly. Free Docker tool.
Open free tool →
AI Pipeline DAG Builder
Design and analyze AI workflow DAGs. Define nodes, dependencies, and execution costs. Calculate critical path latency and total pipeline cost.
Open free tool →
Multi-Agent Orchestration Planner
Decompose complex goals into multi-agent workflows. Plan agent roles, task dependencies, and data flow. Generate orchestration code for LangGraph, CrewAI, and AutoGen.
Open free tool →
More Free Tools
Related Articles
ETL vs ELT: Modern Data Pipeline Architecture Guide 2026
ETL vs ELT data pipeline comparison 2026: traditional ETL limitations, cloud ELT advantages, dbt for transformation, tools comparison (Fivetran/Airbyte/dbt), and when to use each approach.
AI Agent Orchestration Patterns: CrewAI vs LangGraph vs AutoGen 2026
Deep-dive into multi-agent orchestration patterns in 2026. DAG vs sequential vs hierarchical architectures, state management, CrewAI vs LangGraph vs AutoGen with real code examples and a decision framework.
Apache Airflow vs Prefect: Workflow Orchestration Comparison 2026
Apache Airflow vs Prefect 2026: DAG vs flow syntax, scheduling, dynamic workflows, observability, learning curve, cloud offerings, and which to choose for your data team.
Apache Kafka for Real-Time Data Pipelines: Tutorial 2026
Apache Kafka tutorial 2026: topics, partitions, consumer groups, producers/consumers in Python, Schema Registry, Kafka Connect, exactly-once semantics, and production best practices.
Apache Spark vs Flink: Stream Processing Comparison 2026
Apache Spark vs Apache Flink 2026: architecture differences, true streaming vs micro-batch, latency, exactly-once semantics, SQL support, state management, and when to choose each.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →