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 sudo que pertenezca al grupo docker.
  • 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.

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:

ServicioFunción
postgresBase de datos de metadatos (DAG, ejecuciones, conexiones)
redisCola de mensajes entre el scheduler y los workers
airflow-apiserverInterfaz web y API REST, en el puerto 8080
airflow-schedulerDecide qué tareas ejecutar y cuándo
airflow-dag-processorLee y analiza los archivos de DAG
airflow-workerEjecuta las tareas (Celery)
airflow-triggererGestiona las tareas diferidas
airflow-initMigra 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 @daily o @hourly.
  • catchup=False evita que Airflow lance ejecuciones atrasadas desde start_date hasta hoy al activarlo.
  • retries y retry_delay reintentan 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 el 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.yaml o .env: docker compose up -d.
  • Parar Airflow conservando los datos: docker compose down. Con docker compose down -v se 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.