Apache Airflow es una plataforma para programar y monitorizar flujos de trabajo definidos en Python. Cada flujo es un DAG (grafo acíclico dirigido): un conjunto de tareas con dependencias que Airflow ejecuta según un calendario, con reintentos, registros por tarea e interfaz web. En este tutorial desplegarás Airflow 3 en Ubuntu 24.04 con el archivo de Docker Compose oficial (CeleryExecutor, PostgreSQL y Redis), escribirás y probarás un DAG, definirás conexiones y variables y publicarás la interfaz con Nginx y HTTPS.
Requisitos previos
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 4 GB de RAM (8 GB recomendados), 2 vCPU y 20 GB libres en disco. Con menos memoria los contenedores de Airflow se reinician o no llegan a arrancar.
- Un usuario no root con privilegios
sudoque pertenezca al grupodocker. - Docker Engine y el plugin Docker Compose v2.14 o superior, instalados desde el repositorio oficial de Docker.
- Un subdominio (en esta guía,
airflow.tu_dominio) con un registro DNS A que apunte a la IP pública del servidor. - UFW activo con SSH permitido.
Importanteel archivo de Compose oficial está pensado para aprender y para entornos pequeños. El propio proyecto recomienda el chart de Helm sobre Kubernetes para producción. Esta guía añade lo mínimo para exponerlo de forma segura (puerto local, HTTPS, contraseña propia).
Paso 1: Descargar el archivo de Docker Compose oficial
Crea el directorio del proyecto y descarga el archivo de Compose de la versión estable de Airflow:
mkdir -p ~/airflow
cd ~/airflow
curl -LfO 'https://airflow.apache.org/docs/apache-airflow/stable/docker-compose.yaml'
El archivo define estos servicios:
| Servicio | Función |
|---|---|
postgres | Base de datos de metadatos (DAG, ejecuciones, conexiones) |
redis | Cola de mensajes entre el scheduler y los workers |
airflow-apiserver | Interfaz web y API REST, en el puerto 8080 |
airflow-scheduler | Decide qué tareas ejecutar y cuándo |
airflow-dag-processor | Lee y analiza los archivos de DAG |
airflow-worker | Ejecuta las tareas (Celery) |
airflow-triggerer | Gestiona las tareas diferidas |
airflow-init | Migra la base de datos y crea el usuario inicial |
En Airflow 2.x el servicio web se llamaba airflow-webserver y no existía airflow-dag-processor. Si descargas un archivo de una versión 2.x, adapta los nombres de los servicios en los comandos siguientes.
Paso 2: Preparar directorios, usuario y puerto
Crea los directorios que el archivo monta en los contenedores:
mkdir -p ~/airflow/{dags,logs,plugins,config}
Crea el archivo .env. AIRFLOW_UID hace que los contenedores escriban con tu UID, de modo que los archivos de dags y logs sigan siendo tuyos. Las otras dos variables sustituyen el usuario por defecto airflow/airflow:
nano ~/airflow/.env
AIRFLOW_UID=1000
_AIRFLOW_WWW_USER_USERNAME=admin
_AIRFLOW_WWW_USER_PASSWORD=your_strong_password
Sustituye 1000 por el resultado de id -u y your_strong_password por una contraseña robusta. Protege el archivo:
chmod 600 ~/airflow/.env
El archivo oficial publica la interfaz en todas las interfaces (8080:8080). Docker se salta las reglas de UFW, así que quedaría accesible desde Internet sin cifrar. Publícala solo en local:
sed -i 's/"8080:8080"/"127.0.0.1:8080:8080"/' ~/airflow/docker-compose.yaml
Desactiva también los DAG de ejemplo, que llenan la interfaz con decenas de flujos de demostración:
sed -i "s/AIRFLOW__CORE__LOAD_EXAMPLES: 'true'/AIRFLOW__CORE__LOAD_EXAMPLES: 'false'/" ~/airflow/docker-compose.yaml
Comprueba ambos cambios:
grep -E '8080:8080|LOAD_EXAMPLES' ~/airflow/docker-compose.yaml
AIRFLOW__CORE__LOAD_EXAMPLES: 'false'
- "127.0.0.1:8080:8080"
Paso 3: Inicializar y arrancar Airflow
El servicio airflow-init comprueba los recursos, migra la base de datos y crea el usuario administrador. Ejecútalo en primer plano para ver el resultado:
cd ~/airflow
docker compose up airflow-init
Al terminar debe salir con código 0:
airflow-init-1 exited with code 0
Si aparece un aviso de memoria o CPU insuficientes, el servidor no cumple los requisitos y es probable que los servicios fallen más adelante.
Arranca el resto de servicios en segundo plano:
docker compose up -d
Tras uno o dos minutos, todos deben aparecer como healthy:
docker compose ps --format "table {{.Service}}\t{{.Status}}"
SERVICE STATUS
airflow-apiserver Up 2 minutes (healthy)
airflow-dag-processor Up 2 minutes (healthy)
airflow-scheduler Up 2 minutes (healthy)
airflow-triggerer Up 2 minutes (healthy)
airflow-worker Up 2 minutes (healthy)
postgres Up 3 minutes (healthy)
redis Up 3 minutes (healthy)
Paso 4: Publicar la interfaz con Nginx y HTTPS
Instala Nginx y Certbot:
sudo apt update
sudo apt install nginx certbot python3-certbot-nginx
Crea el bloque de servidor:
sudo nano /etc/nginx/sites-available/airflow
server {
listen 80;
listen [::]:80;
server_name airflow.tu_dominio;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Activa el sitio, valida la configuración y recarga Nginx:
sudo ln -s /etc/nginx/sites-available/airflow /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Abre los puertos web y solicita el certificado:
sudo ufw allow 'Nginx Full'
sudo certbot --nginx -d airflow.tu_dominio
Congratulations! You have successfully enabled HTTPS on https://airflow.tu_dominio
Abre https://airflow.tu_dominio e inicia sesión con el usuario y la contraseña del archivo .env. Como has desactivado los ejemplos, la lista de DAG estará vacía.
Paso 5: Escribir tu primer DAG
Airflow detecta cualquier archivo Python en ~/airflow/dags, montado en los contenedores como /opt/airflow/dags. Crea un DAG sencillo de extracción, transformación y carga:
nano ~/airflow/dags/etl_ejemplo.py
from datetime import datetime, timedelta
from airflow.providers.standard.operators.bash import BashOperator
from airflow.sdk import dag, task
@dag(
schedule="0 6 * * *",
start_date=datetime(2026, 1, 1),
catchup=False,
default_args={"retries": 2, "retry_delay": timedelta(minutes=5)},
tags=["ejemplo"],
)
def etl_ejemplo():
@task
def extraer() -> list[int]:
return [12, 7, 30]
@task
def transformar(valores: list[int]) -> int:
return sum(valores)
@task
def cargar(total: int) -> None:
print(f"Total procesado: {total}")
avisar = BashOperator(
task_id="avisar",
bash_command='echo "ETL terminado para {{ ds }}"',
)
cargar(transformar(extraer())) >> avisar
etl_ejemplo()
Qué define este archivo:
schedule="0 6 * * *"ejecuta el DAG cada día a las 06:00 UTC. También admite valores como@dailyo@hourly.catchup=Falseevita que Airflow lance ejecuciones atrasadas desdestart_datehasta hoy al activarlo.retriesyretry_delayreintentan dos veces una tarea fallida, con cinco minutos de espera.- Las funciones con
@task(API TaskFlow) se pasan los resultados entre sí; Airflow deduce las dependencias.>>añade una dependencia explícita con elBashOperator.
Notaeste DAG usa los módulos de Airflow 3 (
airflow.sdky el proveedorstandard). En Airflow 2.x los equivalentes sonfrom airflow.decorators import dag, taskyfrom airflow.operators.bash import BashOperator.
Paso 6: Validar y ejecutar el DAG
El procesador de DAG revisa la carpeta periódicamente, así que el DAG puede tardar un par de minutos en aparecer. Comprueba primero que no hay errores de importación:
docker compose exec airflow-scheduler airflow dags list-import-errors
No data found
Si hay un error de sintaxis o un import incorrecto, este comando muestra el archivo y la traza. Confirma que Airflow ya conoce el DAG:
docker compose exec airflow-scheduler airflow dags list | grep etl_ejemplo
Ejecuta una prueba completa sin esperar al calendario. airflow dags test ejecuta todas las tareas en un solo proceso y muestra sus registros en la terminal:
docker compose exec airflow-scheduler airflow dags test etl_ejemplo
Entre la salida deberías ver el resultado de la tarea cargar:
... Total procesado: 49
Los DAG nuevos están pausados por defecto. Actívalo y lanza una ejecución real, que pasará por el scheduler y los workers de Celery:
docker compose exec airflow-scheduler airflow dags unpause etl_ejemplo
docker compose exec airflow-scheduler airflow dags trigger etl_ejemplo
En la interfaz web, abre el DAG etl_ejemplo: verás la ejecución en la vista de cuadrícula y, al hacer clic en cada tarea, sus registros.
Paso 7: Definir conexiones y variables
Las conexiones guardan los datos de acceso a sistemas externos para no escribirlos en el código de los DAG. Crea una conexión a PostgreSQL con una URI:
docker compose exec airflow-scheduler airflow connections add postgres_origen \
--conn-uri 'postgres://usuario:[email protected]_dominio:5432/ventas'
Compruébala:
docker compose exec airflow-scheduler airflow connections get postgres_origen
Las tareas la usan por su identificador, por ejemplo con PostgresHook(postgres_conn_id="postgres_origen") del proveedor de PostgreSQL, incluido en la imagen oficial.
Las variables guardan valores de configuración que cambian entre entornos:
docker compose exec airflow-scheduler airflow variables set entorno produccion
docker compose exec airflow-scheduler airflow variables get entorno
produccion
Léelas dentro de una tarea, no en el nivel superior del archivo. El código de nivel superior se ejecuta cada vez que Airflow analiza el DAG y consultaría la base de datos continuamente:
from airflow.sdk import Variable, task
@task
def mostrar_entorno() -> None:
print(Variable.get("entorno"))
Las conexiones y variables también se gestionan desde la interfaz, en el menú Admin.
Paso 8: Operación diaria
Algunos comandos útiles desde ~/airflow:
- Ver los registros del scheduler:
docker compose logs -f airflow-scheduler. - Reiniciar todo tras cambiar
docker-compose.yamlo.env:docker compose up -d. - Parar Airflow conservando los datos:
docker compose down. Condocker compose down -vse borra también el volumen de PostgreSQL con todo el historial.
Los registros de las tareas se acumulan en ~/airflow/logs y pueden ocupar mucho con el tiempo. Revisa su tamaño periódicamente:
du -sh ~/airflow/logs
Para añadir paquetes de Python que necesiten tus DAG, no los instales dentro de los contenedores en ejecución: se pierden al recrearlos. Crea una imagen propia a partir de apache/airflow con un Dockerfile y cambia la línea image: del archivo de Compose, como indica la documentación oficial.
Solución de problemas
El DAG no aparece en la interfaz. Espera un par de minutos y ejecuta airflow dags list-import-errors. Comprueba también que el archivo está en ~/airflow/dags y que tiene la llamada final etl_ejemplo() que registra el DAG.
Las tareas se quedan en estado queued. No hay workers disponibles. Revisa docker compose ps y docker compose logs airflow-worker; si el worker se reinicia, suele ser por falta de memoria.
Errores Permission denied en logs o dags. AIRFLOW_UID en .env no coincide con tu usuario. Corrígelo, ajusta el propietario con sudo chown -R $(id -u):0 ~/airflow/{dags,logs,plugins,config} y recrea los contenedores con docker compose up -d.
502 Bad Gateway en Nginx. airflow-apiserver aún está arrancando o no está sano. Revisa su estado y sus registros con docker compose logs airflow-apiserver.
Conclusión
Tienes Apache Airflow 3 funcionando con Docker Compose, CeleryExecutor, PostgreSQL y Redis, con la interfaz protegida por HTTPS y un DAG probado de principio a fin. Como siguientes pasos puedes configurar notificaciones por correo o Slack para tareas fallidas, crear una imagen propia con las dependencias de tus DAG y versionar la carpeta dags en un repositorio Git para desplegar cambios de forma controlada.
