--- title: "Cómo instalar Temporal en Ubuntu 24.04 con un worker Python" description: "Instala el servidor de Temporal en Ubuntu 24.04 como servicio de systemd, crea un namespace y ejecuta tu primer workflow duradero con reintentos en Python." category: "Automatización de Workflows" language: es source: https://cubepath.com/es/docs/automatización-de-workflows/instalacion-de-temporal-motor-de-workflows updated: 2026-09-25 --- # Cómo instalar Temporal en Ubuntu 24.04 con un worker Python Temporal es un motor de workflows duraderos: guarda el historial de cada ejecución en su base de datos, de modo que si un proceso se cae a mitad de un flujo, otro lo retoma exactamente donde se quedó, con reintentos automáticos en los pasos que fallan. Tu código de negocio corre en *workers* que tú despliegas, y el servidor de Temporal solo coordina y persiste el estado. En este tutorial instalarás la CLI de Temporal en Ubuntu 24.04, ejecutarás su servidor integrado como servicio de systemd con persistencia en disco, crearás un namespace y escribirás y ejecutarás un workflow en Python con reintentos. ## Requisitos previos - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM. - Un usuario no root con privilegios `sudo`. - Python 3.10 o superior (Ubuntu 24.04 incluye Python 3.12). > Nota: el servidor que instalarás aquí es el que integra la CLI de Temporal (`temporal server start-dev`). Usa SQLite, corre en un solo proceso e incluye la interfaz web, lo que lo hace ideal para desarrollo, pruebas y entornos internos pequeños. Para producción con alta disponibilidad, Temporal recomienda desplegar el servidor con su chart de Helm sobre Kubernetes y una base de datos PostgreSQL, MySQL o Cassandra, o usar Temporal Cloud. El código de los workers y los comandos de la CLI de esta guía son los mismos en ambos casos. ## Paso 1: Instalar la CLI de Temporal La CLI de Temporal es un único binario que sirve tanto para administrar un clúster como para lanzar el servidor integrado. Descarga la última versión para Linux x86_64 desde el servidor de descargas oficial: ```bash cd /tmp curl -fLo temporal_cli.tar.gz "https://temporal.download/cli/archive/latest?platform=linux&arch=amd64" tar xzf temporal_cli.tar.gz temporal sudo install -m 0755 temporal /usr/local/bin/temporal ``` En servidores ARM64 cambia `arch=amd64` por `arch=arm64`. Comprueba la instalación: ```bash temporal --version ``` ```output temporal version 1.4.1 ``` Tus números de versión pueden ser distintos. ## Paso 2: Ejecutar el servidor de Temporal como servicio Sin opciones, `temporal server start-dev` guarda los datos en memoria y los pierde al reiniciar. Para conservarlos, crea un usuario de sistema y un directorio donde guardar la base de datos SQLite: ```bash sudo useradd --system --home-dir /var/lib/temporal --create-home --shell /usr/sbin/nologin temporal ``` Crea la unidad de systemd: ```bash sudo nano /etc/systemd/system/temporal.service ``` ```ini [Unit] Description=Temporal development server Wants=network-online.target After=network-online.target [Service] User=temporal Group=temporal WorkingDirectory=/var/lib/temporal ExecStart=/usr/local/bin/temporal server start-dev --db-filename /var/lib/temporal/temporal.db --ip 127.0.0.1 --port 7233 --ui-port 8233 Restart=on-failure [Install] WantedBy=multi-user.target ``` - `--db-filename` activa la persistencia en un archivo SQLite. - `--ip 127.0.0.1` hace que el servidor (puerto 7233, gRPC) y la interfaz web (puerto 8233) solo escuchen en local. El servidor no tiene autenticación en este modo, así que no lo expongas a Internet. Recarga systemd y arranca el servicio: ```bash sudo systemctl daemon-reload sudo systemctl enable --now temporal sudo systemctl status temporal ``` ```output ● temporal.service - Temporal development server Loaded: loaded (/etc/systemd/system/temporal.service; enabled; preset: enabled) Active: active (running) since Thu 2026-09-25 11:02:14 UTC; 5s ago ``` Comprueba que el servidor responde: ```bash temporal operator cluster health ``` ```output SERVING ``` ## Paso 3: Crear un namespace Un namespace es la unidad de aislamiento de Temporal: cada uno tiene sus propios workflows, colas y período de retención del historial. El servidor trae un namespace `default`, pero es buena práctica crear uno por aplicación. Crea el namespace `pedidos` con una retención de 7 días para el historial de los workflows terminados: ```bash temporal operator namespace create --namespace pedidos --retention 168h ``` ```output Namespace pedidos successfully registered. ``` Comprueba su configuración: ```bash temporal operator namespace describe --namespace pedidos ``` La salida incluye el nombre, el estado `Registered` y la retención de 168 horas. ## Paso 4: Preparar el proyecto Python Los workflows y las actividades se escriben con el SDK de Temporal. Instala el soporte de entornos virtuales y crea el proyecto: ```bash sudo apt update sudo apt install python3-venv mkdir -p ~/pedidos-temporal cd ~/pedidos-temporal python3 -m venv venv ./venv/bin/pip install temporalio ``` Comprueba que el paquete se ha instalado: ```bash ./venv/bin/pip show temporalio | head -n 2 ``` ```output Name: temporalio Version: 1.18.0 ``` ## Paso 5: Escribir las actividades Una *actividad* es una función que hace trabajo con efectos externos: llamar a una API, escribir en una base de datos, enviar un email. Es la parte que puede fallar y que Temporal reintenta. Crea el archivo de actividades: ```bash nano ~/pedidos-temporal/activities.py ``` ```python import random from dataclasses import dataclass from temporalio import activity from temporalio.exceptions import ApplicationError @dataclass class Pedido: pedido_id: str total: float email: str @activity.defn async def cobrar_pago(pedido: Pedido) -> str: if pedido.total <= 0: # Un importe inválido no se arregla reintentando. raise ApplicationError("Importe inválido", non_retryable=True) # Simula una pasarela de pagos que falla la mitad de las veces. if random.random() < 0.5: raise RuntimeError("La pasarela de pagos no responde") activity.logger.info("Pago cobrado para el pedido %s", pedido.pedido_id) return f"pago-{pedido.pedido_id}" @activity.defn async def enviar_confirmacion(pedido: Pedido) -> None: activity.logger.info("Confirmación enviada a %s", pedido.email) ``` `cobrar_pago` falla de forma aleatoria para que puedas ver los reintentos en acción. `ApplicationError` con `non_retryable=True` marca los errores que no tiene sentido reintentar. ## Paso 6: Escribir el workflow El *workflow* orquesta las actividades. Su código debe ser determinista: no puede llamar a redes, leer la hora del sistema ni usar números aleatorios directamente, porque Temporal lo vuelve a ejecutar para reconstruir su estado a partir del historial. Todo lo que no sea determinista va en actividades. ```bash nano ~/pedidos-temporal/workflows.py ``` ```python from datetime import timedelta from temporalio import workflow from temporalio.common import RetryPolicy with workflow.unsafe.imports_passed_through(): from activities import Pedido, cobrar_pago, enviar_confirmacion @workflow.defn class ProcesarPedido: @workflow.run async def run(self, pedido: Pedido) -> str: pago_id = await workflow.execute_activity( cobrar_pago, pedido, start_to_close_timeout=timedelta(seconds=30), retry_policy=RetryPolicy( initial_interval=timedelta(seconds=1), backoff_coefficient=2.0, maximum_interval=timedelta(seconds=30), maximum_attempts=10, ), ) await workflow.execute_activity( enviar_confirmacion, pedido, start_to_close_timeout=timedelta(seconds=30), ) return pago_id ``` - `start_to_close_timeout` es el tiempo máximo de cada intento de la actividad. Es obligatorio definir al menos un timeout. - La `RetryPolicy` reintenta el cobro hasta 10 veces, esperando 1, 2, 4, 8... segundos entre intentos, con un máximo de 30. Sin política explícita, Temporal reintenta indefinidamente. - `imports_passed_through()` permite importar el módulo de actividades desde el sandbox en el que se ejecutan los workflows. ## Paso 7: Crear el worker y lanzar un workflow El *worker* es el proceso que escucha una cola de tareas (task queue) y ejecuta el código de workflows y actividades. Créalo: ```bash nano ~/pedidos-temporal/worker.py ``` ```python import asyncio import logging from temporalio.client import Client from temporalio.worker import Worker from activities import cobrar_pago, enviar_confirmacion from workflows import ProcesarPedido async def main() -> None: logging.basicConfig(level=logging.INFO) client = await Client.connect("127.0.0.1:7233", namespace="pedidos") worker = Worker( client, task_queue="pedidos-queue", workflows=[ProcesarPedido], activities=[cobrar_pago, enviar_confirmacion], ) await worker.run() if __name__ == "__main__": asyncio.run(main()) ``` Crea también un script que inicie un workflow y espere su resultado: ```bash nano ~/pedidos-temporal/starter.py ``` ```python import asyncio import sys from temporalio.client import Client from activities import Pedido from workflows import ProcesarPedido async def main(pedido_id: str) -> None: client = await Client.connect("127.0.0.1:7233", namespace="pedidos") resultado = await client.execute_workflow( ProcesarPedido.run, Pedido(pedido_id=pedido_id, total=150.0, email="cliente@example.com"), id=f"pedido-{pedido_id}", task_queue="pedidos-queue", ) print(f"Workflow completado: {resultado}") if __name__ == "__main__": asyncio.run(main(sys.argv[1])) ``` El `id` del workflow identifica la ejecución de forma única: Temporal no permite dos ejecuciones abiertas con el mismo ID, lo que evita procesar dos veces el mismo pedido. Arranca el worker en una terminal: ```bash cd ~/pedidos-temporal ./venv/bin/python worker.py ``` En una segunda terminal, lanza un workflow: ```bash cd ~/pedidos-temporal ./venv/bin/python starter.py 1001 ``` ```output Workflow completado: pago-1001 ``` En la terminal del worker verás advertencias con el error `La pasarela de pagos no responde` cuando la actividad falla, seguidas del cobro correcto en un reintento posterior. Detén el worker con `Ctrl+C` cuando termines. ## Paso 8: Inspeccionar los workflows La CLI permite consultar el estado y el historial completo de cualquier ejecución. Lista los workflows del namespace: ```bash temporal workflow list --namespace pedidos ``` ```output Status WorkflowId Type StartTime Completed pedido-1001 ProcesarPedido 1 minute ago ``` Muestra el historial de eventos. Cada intento fallido de la actividad queda registrado, y el campo `attempt` del evento `ActivityTaskStarted` indica cuántos intentos hicieron falta: ```bash temporal workflow show --workflow-id pedido-1001 --namespace pedidos ``` La interfaz web ofrece la misma información de forma visual. Como solo escucha en local, ábrela a través de un túnel SSH desde tu ordenador: ```bash ssh -L 8233:127.0.0.1:8233 your_user@your_server_ip ``` Con el túnel abierto, visita `http://localhost:8233` en tu navegador, selecciona el namespace `pedidos` y abre `pedido-1001` para ver la línea de tiempo de las actividades y sus reintentos. ## Paso 9: Ejecutar el worker como servicio En un entorno real el worker debe arrancar solo y reiniciarse si falla. Crea una unidad de systemd que lo ejecute con tu usuario: ```bash sudo nano /etc/systemd/system/pedidos-worker.service ``` ```ini [Unit] Description=Temporal worker for pedidos After=temporal.service Wants=temporal.service [Service] User=your_user WorkingDirectory=/home/your_user/pedidos-temporal ExecStart=/home/your_user/pedidos-temporal/venv/bin/python worker.py Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target ``` Sustituye `your_user` por tu usuario. Activa el servicio y comprueba sus logs: ```bash sudo systemctl daemon-reload sudo systemctl enable --now pedidos-worker journalctl -u pedidos-worker -n 20 --no-pager ``` Lanza otro workflow para confirmar que el worker del servicio lo procesa: ```bash cd ~/pedidos-temporal ./venv/bin/python starter.py 1002 ``` ```output Workflow completado: pago-1002 ``` Para comprobar la durabilidad, lanza un pedido nuevo y reinicia el worker mientras la actividad está reintentando (`sudo systemctl restart pedidos-worker`). El workflow continuará tras el reinicio sin repetir los pasos que ya habían terminado. ## Solución de problemas **`Failed client connect` o `connection refused` en el puerto 7233.** El servidor no está en marcha. Revisa `sudo systemctl status temporal` y `journalctl -u temporal -n 50`. **`Namespace pedidos is not found`.** El namespace no existe en este servidor. Créalo con `temporal operator namespace create --namespace pedidos`. Si acabas de crearlo, espera unos segundos a que se propague. **El workflow se queda en `Running` y no avanza.** No hay ningún worker escuchando la cola `pedidos-queue` en ese namespace. Comprueba los pollers con `temporal task-queue describe --task-queue pedidos-queue --namespace pedidos`; si la lista está vacía, arranca el worker. **Error de no determinismo (`Nondeterminism error`) tras cambiar el código.** Has modificado la secuencia de actividades de un workflow que ya tenía ejecuciones abiertas. Termina esas ejecuciones con `temporal workflow terminate --workflow-id --namespace pedidos` o usa el versionado de workflows del SDK para cambios en producción. ## Conclusión Tienes un servidor de Temporal persistente como servicio de systemd, un namespace propio y un workflow en Python que sobrevive a fallos de sus actividades y a reinicios del worker. Como siguientes pasos, puedes usar señales y queries para interactuar con workflows en ejecución, programar ejecuciones periódicas con `temporal schedule create` y, cuando pases a producción, desplegar el servidor con el chart de Helm oficial sobre PostgreSQL manteniendo el mismo código de workers.