Instalación de Kestra en un VPS: orquestación de tareas, Docker, PostgreSQL, SSL y programaciones
Resumen
Kestra es una plataforma self-hosted para la orquestación de tareas, pipelines ETL, automatización de API y programaciones. En esta guía desplegará Kestra en un VPS con Ubuntu mediante Docker Compose, conectará PostgreSQL, protegerá la interfaz con el proxy HTTPS Caddy, creará su primer workflow con una programación cron y configurará copias de seguridad.
- Para una instancia pequeña de production basta con un VPS con 2 vCPU, 4 GB de RAM y 40–60 GB de NVMe.
- Kestra y PostgreSQL se ejecutarán en contenedores Docker independientes.
- Caddy emitirá y renovará automáticamente el certificado TLS de Let’s Encrypt.
- Todas las contraseñas se almacenarán en el archivo
.env, no en Docker Compose ni en el workflow. - El primer workflow se ejecutará manualmente y según una programación cron.
- Las copias de seguridad de PostgreSQL y la configuración se ejecutarán mediante cron y se enviarán a un almacenamiento externo.
Qué configuramos y por qué
Kestra es una plataforma de orquestación en la que las tareas se describen mediante archivos YAML declarativos. Es adecuada para ejecutar procesos ETL, procesar archivos, sincronizar servicios SaaS, realizar llamadas a API, generar informes periódicos, mantener la infraestructura, ejecutar scripts de Python, consultas SQL y jobs en contenedores.
A diferencia de un simple cron, Kestra almacena el historial de ejecuciones, los parámetros, los logs, los estados de las tareas, los errores y los reintentos. La interfaz web permite ejecutar workflows manualmente, transmitir datos de entrada, revisar la ejecución paso a paso y volver a ejecutar procesos fallidos.
Después de completar las instrucciones, en el servidor funcionarán los siguientes componentes:
- Kestra Server con interfaz web y API;
- PostgreSQL 16 como base de datos fiable para workflows, colas, execution history y metadatos;
- Docker Compose para gestionar los servicios;
- Caddy como reverse proxy con HTTPS automático;
- Firewall UFW, claves SSH y Fail2ban para la protección básica del VPS;
- backup periódico de PostgreSQL, la configuración y los datos persistentes de Kestra;
- un ejemplo de workflow con ejecución manual y programación cron.
Qué se puede automatizar mediante Kestra
| Escenario | Ejemplo de workflow | Frecuencia de ejecución |
|---|---|---|
| ETL y analítica | Obtener un CSV de S3, limpiar los datos y cargarlos en PostgreSQL | Cada hora o diariamente |
| Automatización DevOps | Comprobar las copias de seguridad, recopilar métricas y enviar una notificación | Cada 15 minutos |
| Integraciones SaaS | Obtener leads de un formulario y crear un registro en el CRM | Mediante webhook o cada 5 minutos |
| Procesamiento de archivos | Convertir los PDF cargados, extraer el texto y guardar el resultado | Por evento |
| Informes | Ejecutar SQL, generar un informe y enviarlo a Telegram o por email | Los días laborables a las 09:00 |
Kestra cloud-managed o self-hosted
La versión Managed libera al equipo de las tareas de actualización, almacenamiento de datos y escalado, pero añade un pago periódico y dependencia de una plataforma externa. Kestra self-hosted en un VPS requiere una configuración inicial, pero permite controlar la ubicación de almacenamiento de datos, los límites, las reglas de red, los plugins, las direcciones IP salientes y los costes.
La opción self-hosted resulta especialmente conveniente si los workflows trabajan con bases de datos internas, API privadas, repositorios Git cerrados o documentos sensibles. El servidor puede ubicarse en la jurisdicción necesaria, conectarse a una red VPN y restringir el acceso al panel por IP o mediante autenticación HTTP adicional.
No utilice la base de datos H2 integrada de Kestra para un despliegue en production. Es adecuada para el desarrollo local y las pruebas, pero para un VPS con programaciones reales, registros y varios workflows utilice PostgreSQL.
Qué configuración de VPS se necesita para esta tarea
La carga de Kestra depende no tanto del número de archivos YAML como de la naturaleza de las tareas ejecutadas. Si los workflows solo llaman a API y ejecutan consultas SQL, los requisitos son moderados. Si ejecutan procesamiento de archivos con Python, contenedores Docker, automatización de navegador o transformaciones de tablas grandes, se necesitan CPU, RAM y un disco rápido adicionales.
| Perfil | CPU | RAM | Disco | Adecuado para |
|---|---|---|---|---|
| Pruebas | 1 vCPU | 2 GB | 25 GB NVMe | Familiarización con la interfaz, 1–3 workflows ligeros |
| Production mínimo | 2 vCPU | 4 GB | 40–60 GB NVMe | Hasta decenas de tareas de API, SQL y cron al día |
| Operativo | 4 vCPU | 8 GB | 100 GB NVMe | Un equipo, workflows paralelos, logs grandes |
| Cálculos intensivos | 8 vCPU+ | 16–32 GB+ | 200 GB NVMe+ | Python, jobs Docker, archivos grandes, ML y ETL |
Una opción práctica para comenzar es 2 vCPU, 4 GB de RAM, 60 GB de NVMe y un canal de al menos 100 Mbit/s. Este servidor normalmente soporta Kestra, PostgreSQL, Caddy, los servicios básicos del sistema y un número moderado de tareas ejecutándose simultáneamente. Si planea ejecutar workflows con contenedores Docker, comience con 4 vCPU y 8 GB de RAM.
Para este escenario puede contratar un VPS con las características indicadas, instalar Ubuntu Server 24.04 LTS y configurar de inmediato el registro DNS para el futuro dominio de Kestra.
Cuándo se necesita un servidor dedicated
Un servidor dedicado no se necesita por la propia interfaz de Kestra, sino para cargas de trabajo pesadas. Cambie a un dedicated si procesa regularmente decenas de gigabytes de archivos, ejecuta muchos contenedores en paralelo, necesita recursos de CPU garantizados, un IOPS alto o almacena localmente un gran volumen de logs de ejecución.
Un dedicated también tiene sentido si Kestra ejecuta procesos CI internos, navegadores Playwright, conversión de vídeo, modelos de machine learning o tareas Spark a gran escala. Para la automatización habitual de API, SQL y copias de seguridad, un VPS es más sencillo, económico y fácil de escalar verticalmente.
Cómo elegir la ubicación
La ubicación influye en la latencia hasta la base de datos, las API externas y los usuarios de la interfaz web. Si Kestra se conecta a una base de datos en un centro de datos europeo, elija un VPS cercano. Si las tareas trabajan con API locales o una red corporativa, es más importante la proximidad a la puerta de enlace VPN y un enrutamiento estable.
Para el certificado TLS y los webhook públicos, hay otro aspecto más importante: el nombre de dominio debe apuntar a la IP pública del servidor, y los puertos 80 y 443 deben estar accesibles desde Internet. Antes de instalar Caddy, cree un registro A, por ejemplo kestra.example.com, que apunte a la IP del VPS.
Preparación del servidor
A continuación se presupone un servidor limpio con Ubuntu Server 24.04 LTS y acceso SSH con el usuario root. Ejecute las acciones iniciales desde el terminal local. Sustituya SERVER_IP por la dirección externa de su VPS.
Creación del administrador y configuración de la clave SSH
Primero cree un usuario independiente. No ejecute Docker Compose ni trabaje con Kestra como root si no es necesario. El nombre de usuario del ejemplo es ops.
ssh root@SERVER_IP
adduser ops
usermod -aG sudo ops
Añada una clave SSH pública para el nuevo usuario. El comando siguiente se ejecuta en el equipo local donde ya existe la clave ~/.ssh/id_ed25519.pub.
ssh-copy-id ops@SERVER_IP
ssh ops@SERVER_IP
Compruebe que el acceso con el nuevo usuario funciona y solo después desactive el acceso de root y la autenticación mediante contraseña. Abra una segunda sesión SSH para no perder el acceso en caso de error de configuración.
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no
X11Forwarding no
AllowUsers ops
Compruebe la sintaxis de la configuración y reinicie el servicio SSH.
sudo sshd -t && sudo systemctl restart ssh
Actualización del sistema y utilidades básicas
Actualice los paquetes a su estado más reciente. En un servidor de production es útil activar las actualizaciones automáticas de seguridad, pero las actualizaciones de Docker y Kestra deben realizarse por separado en una ventana controlada.
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release \
ufw fail2ban unattended-upgrades jq htop vim cron
Compruebe la versión del kernel y si es necesario reiniciar. Si existe el archivo /var/run/reboot-required, reinicie el VPS en una ventana adecuada.
test -f /var/run/reboot-required && cat /var/run/reboot-required
sudo reboot
Firewall UFW y Fail2ban
En el servidor solo deben estar abiertos SSH, HTTP y HTTPS. PostgreSQL no se publica externamente en el puerto 5432: Kestra accede a él mediante la red interna de Docker.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Fail2ban bloqueará las direcciones IP después de varios intentos fallidos de acceso por SSH. Cree una configuración local mínima del jail.
sudo tee /etc/fail2ban/jail.d/sshd.local > /dev/null <<'EOF'
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Si utiliza un puerto SSH no estándar, permítalo primero en UFW y compruebe el nuevo acceso; después cierre el puerto antiguo. No cambie el puerto SSH como única medida de seguridad: las claves, la desactivación del root-login y Fail2ban son más importantes.
Instalación del software — paso a paso
Para el despliegue utilizaremos Docker Engine 27+ y Docker Compose Plugin v2. Kestra quedará fijada en una etiqueta concreta de la imagen, no en latest. Esto protege contra actualizaciones accidentales al reiniciar los contenedores.
Instalación de Docker Engine y Docker Compose
Añada la clave oficial y el repositorio de Docker para Ubuntu. Los comandos se ejecutan con el usuario ops mediante sudo.
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
Añada el repositorio, actualice el índice de paquetes e instale Docker Engine, CLI y Compose Plugin.
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
Añada el usuario al grupo Docker. Después del comando, salga de la sesión SSH y vuelva a conectarse: la pertenencia al grupo se aplica únicamente a las nuevas sesiones.
sudo usermod -aG docker ops
exit
Después de volver a iniciar sesión, compruebe Docker. El contenedor hello-world debe finalizar sin errores.
docker --version
docker compose version
docker run --rm hello-world
El grupo
dockerotorga de facto al usuario permisos comparables a los de root. Añada únicamente administradores de confianza. Para una instalación pequeña, resulta más cómodo que ejecutar cada comando mediante sudo.
Instalación de Caddy
Caddy recibirá el tráfico en los puertos 80 y 443, obtendrá automáticamente un certificado de Let’s Encrypt y reenviará las solicitudes a Kestra en localhost. Instale Caddy mediante el paquete del repositorio oficial.
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy
Compruebe que Caddy está en ejecución. Mientras no se modifique la configuración, no hará proxy de Kestra.
sudo systemctl enable --now caddy
sudo systemctl status caddy --no-pager
Creación de la estructura de directorios
Almacene la configuración del proyecto en un solo directorio. En el ejemplo se utiliza /opt/kestra. Los datos persistentes estarán en Docker named volumes, mientras que los archivos de configuración, las exportaciones de workflows y las copias de seguridad estarán en el host.
sudo mkdir -p /opt/kestra/{config,backups,flows}
sudo chown -R ops:ops /opt/kestra
cd /opt/kestra
pwd
En 2026, antes de una instalación en producción, compruebe obligatoriamente la etiqueta estable actual en el registro de imágenes de Kestra y el changelog del proyecto. En el ejemplo se utiliza la etiqueta fijada v0.24.0 como plantilla. No sustituya la etiqueta por latest; actualícela conscientemente después de realizar una copia de seguridad y revisar las release notes.
Configuración de Kestra, PostgreSQL y SSL
Preparación de secretos
Genere contraseñas largas independientes para PostgreSQL y la autenticación HTTP básica de Caddy. No añada el archivo .env a Git ni lo envíe por mensajería. Los permisos 600 impiden que otros usuarios del servidor lean el archivo.
cd /opt/kestra
POSTGRES_PASSWORD=$(openssl rand -base64 32 | tr -d '\n')
CADDY_PASSWORD=$(openssl rand -base64 24 | tr -d '\n')
cat > .env <<EOF
POSTGRES_DB=kestra
POSTGRES_USER=kestra
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
KESTRA_DOMAIN=kestra.example.com
CADDY_USER=admin
CADDY_PASSWORD=${CADDY_PASSWORD}
EOF
chmod 600 .env
Sustituya kestra.example.com por su propio dominio. Antes de iniciar Caddy, compruebe el DNS: el comando debe devolver la IP pública de su VPS.
source /opt/kestra/.env
getent ahostsv4 "$KESTRA_DOMAIN"
curl -4 ifconfig.me ; echo
Configuración de Kestra
Cree el archivo config/application.yml. En él, Kestra utiliza PostgreSQL como repository y queue backend. El almacenamiento local es adecuado para un solo nodo de Kestra. Al pasar a varios servidores, el almacenamiento debe trasladarse a un almacenamiento de objetos compatible con S3.
cat > /opt/kestra/config/application.yml <<'EOF'
datasources:
postgres:
url: jdbc:postgresql://postgres:5432/${POSTGRES_DB}
driverClassName: org.postgresql.Driver
username: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
kestra:
repository:
type: postgres
queue:
type: postgres
storage:
type: local
local:
base-path: /app/storage
tasks:
tmp-dir:
path: /tmp/kestra-wd/tmp
url: https://${KESTRA_DOMAIN}/
server:
basic-auth:
enabled: false
logger:
levels:
io.kestra: INFO
EOF
La autenticación básica de Kestra está desactivada porque Caddy protegerá el acceso externo. Esto simplifica el trabajo con el navegador y la API, pero significa que Caddy debe seguir siendo el único punto de entrada. El puerto de Kestra no se publica externamente.
Docker Compose para Kestra y PostgreSQL
Cree compose.yml. PostgreSQL no tiene una sección ports; por tanto, solo está disponible para los contenedores de la red interna de Docker. Kestra se publica en 127.0.0.1:8080, por lo que Caddy en el mismo servidor puede acceder a ella, pero Internet no.
cat > /opt/kestra/compose.yml <<'EOF'
services:
postgres:
image: postgres:16.6-alpine
container_name: kestra-postgres
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
networks:
- kestra_internal
kestra:
image: kestra/kestra:v0.24.0
container_name: kestra
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
KESTRA_DOMAIN: ${KESTRA_DOMAIN}
JAVA_OPTS: "-Xms512m -Xmx1536m"
command: server standalone --config /etc/kestra/application.yml
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./config/application.yml:/etc/kestra/application.yml:ro
- kestra_storage:/app/storage
- kestra_workdir:/tmp/kestra-wd
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health | grep -q UP"]
interval: 30s
timeout: 10s
retries: 10
start_period: 60s
networks:
- kestra_internal
networks:
kestra_internal:
name: kestra_internal
volumes:
postgres_data:
kestra_storage:
kestra_workdir:
EOF
El parámetro JAVA_OPTS limita el heap de Java a 1,5 GB. En un VPS con 4 GB de RAM, es un punto de partida razonable: el resto de la memoria queda para PostgreSQL, el sistema operativo y la caché de archivos. En un servidor con 8 GB de RAM, puede aumentar -Xmx a 3–4 GB después de observar el consumo de memoria real.
Inicie los servicios en segundo plano y compruebe su estado. El primer inicio puede tardar varios minutos: Kestra crea las tablas y aplica las migraciones en PostgreSQL.
cd /opt/kestra
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 kestra
Compruebe el endpoint local antes de configurar el proxy. La respuesta puede ser JSON o una redirección HTTP, según la versión de Kestra, pero el código no debe ser 502 ni Connection refused.
curl -I http://127.0.0.1:8080/
docker inspect --format='{{.State.Health.Status}}' kestra
docker inspect --format='{{.State.Health.Status}}' kestra-postgres
Configuración de Caddy y HTTPS
Primero, cree un hash bcrypt de la contraseña para Caddy. El comando solicitará la contraseña que guardó en .env. No introduzca la contraseña en la línea de comandos: quedará en el historial del shell.
source /opt/kestra/.env
read -rsp "Password for Caddy user ${CADDY_USER}: " INPUT_PASSWORD; echo
CADDY_HASH=$(printf '%s' "$INPUT_PASSWORD" | caddy hash-password --stdin)
unset INPUT_PASSWORD
printf '%s\n' "$CADDY_HASH"
Cree el Caddyfile. En lugar de la variable de la configuración, introduzca manualmente el hash mostrado en PASTE_BCRYPT_HASH_HERE. Es más seguro que dejar la contraseña en texto plano.
sudo tee /etc/caddy/Caddyfile > /dev/null <<'EOF'
kestra.example.com {
encode zstd gzip
basic_auth {
admin PASTE_BCRYPT_HASH_HERE
}
reverse_proxy 127.0.0.1:8080 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
log {
output file /var/log/caddy/kestra-access.log
format json
}
}
EOF
Sustituya el dominio y el hash en el archivo, cree el directorio de registros y compruebe la sintaxis. Caddy obtiene el certificado automáticamente después de cargar correctamente la configuración, si el DNS ya apunta al servidor y el puerto 80 está accesible desde el exterior.
sudo mkdir -p /var/log/caddy
sudo chown caddy:caddy /var/log/caddy
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager
Abra https://kestra.example.com en el navegador. El navegador solicitará el nombre de usuario y la contraseña de Caddy, tras lo cual se abrirá la interfaz de Kestra. Compruebe también TLS desde la consola.
curl -I https://kestra.example.com
sudo journalctl -u caddy -n 100 --no-pager
Primer workflow y programación cron
Abra Kestra en el navegador, cree un namespace, por ejemplo company.ops, vaya a la sección Flows y cree un flow. Para la primera comprobación, inserte el siguiente YAML. Registra un mensaje en los logs y se ejecuta cada 15 minutos.
id: health_report
namespace: company.ops
description: Проверочный workflow с cron-расписанием
triggers:
- id: every_15_minutes
type: io.kestra.plugin.core.trigger.Schedule
cron: "/15 "
timezone: "Europe/Moscow"
tasks:
- id: write_log
type: io.kestra.plugin.core.log.Log
message: "Kestra работает. Время запуска: {{ execution.startDate }}"
- id: shell_check
type: io.kestra.plugin.scripts.shell.Commands
commands:
- "date -u"
- "echo Workflow finished successfully"
Guarde el workflow, pulse Execute y abra la pestaña Executions. Debe aparecer el estado SUCCESS. Después, espere a una hora múltiplo de 15 minutos o sustituya temporalmente cron por para probarlo cada minuto. Después de la comprobación, restaure la programación normal.
No almacene tokens de API, claves de S3, contraseñas de bases de datos ni secretos de webhooks directamente en YAML. Utilice los secretos de Kestra, variables de entorno o un secret manager externo. Para empezar, puede crear secretos mediante la interfaz en el namespace y, más adelante, conectar Vault, SOPS o un secrets manager en la nube.
Copias de seguridad y mantenimiento
Para restaurar Kestra no solo se necesitan los datos de PostgreSQL. Es necesario conservar los archivos de configuración, el archivo Compose, Caddyfile, el archivo de secretos .env y el persistent storage. PostgreSQL contiene metadatos, execution history, configuraciones y datos de las colas; storage contiene archivos creados por las tareas y los plugins.
| Qué respaldar | Por qué | Con qué frecuencia |
|---|---|---|
| PostgreSQL dump | Workflow metadata, executions, queues, configuraciones | Diariamente, antes de actualizar |
| Docker volume Kestra storage | Archivos de tareas, adjuntos y artefactos locales | Diariamente |
/opt/kestra |
Compose, configuraciones YAML, secretos, scripts | Después de cada cambio y diariamente |
/etc/caddy/Caddyfile |
Proxy HTTPS y acceso al panel | Después de cada cambio |
Script de backup local
El siguiente script crea un volcado comprimido de PostgreSQL y archiva el directorio de configuración. Conserva localmente los últimos 14 días. El backup local no protege contra la eliminación del VPS, por lo que después debe enviarse a un almacenamiento compatible con S3 o a un servidor independiente.
cat > /opt/kestra/backup-kestra.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
BASE="/opt/kestra"
DATE=$(date +%F_%H-%M-%S)
DEST="${BASE}/backups/${DATE}"
mkdir -p "${DEST}"
cd "${BASE}"
docker compose exec -T postgres pg_dump \
-U "${POSTGRES_USER}" \
-d "${POSTGRES_DB}" \
--format=custom \
--file="/tmp/kestra_${DATE}.dump"
docker cp kestra-postgres:"/tmp/kestra_${DATE}.dump" \
"${DEST}/postgres.dump"
docker compose exec -T postgres rm -f "/tmp/kestra_${DATE}.dump"
tar --exclude='backups' \
--exclude='.git' \
-czf "${DEST}/kestra-config.tar.gz" \
-C /opt kestra
docker run --rm \
-v kestra_kestra_storage:/source:ro \
-v "${DEST}":/backup \
alpine:3.20 \
tar -czf /backup/kestra-storage.tar.gz -C /source .
find "${BASE}/backups" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;
EOF
chmod 700 /opt/kestra/backup-kestra.sh
cd /opt/kestra && ./backup-kestra.sh
ls -lah /opt/kestra/backups
El nombre del Docker volume normalmente se forma a partir del nombre del directorio y del nombre del volume. En este caso, el proyecto se ejecuta desde /opt/kestra, por lo que el volume se llama kestra_kestra_storage. Si el comando devuelve un error indicando que el volume no existe, consulte el nombre exacto mediante docker volume ls y corrija el script.
Envío del backup a un almacenamiento externo
Para una protección real, conserve las copias fuera del VPS principal. Opciones prácticas: almacenamiento de objetos compatible con S3, un VPS de backup independiente mediante SSH, un Borg repository o un Restic repository. A continuación se muestra un ejemplo con Restic y un backend compatible con S3. Coloque las variables de acceso en un archivo independiente con permisos 600.
sudo apt install -y restic
sudo tee /etc/kestra-restic.env > /dev/null <<'EOF'
export RESTIC_REPOSITORY="s3:s3.example.net/kestra-backups"
export RESTIC_PASSWORD="CHANGE_TO_A_LONG_UNIQUE_RESTIC_PASSWORD"
export AWS_ACCESS_KEY_ID="CHANGE_ME"
export AWS_SECRET_ACCESS_KEY="CHANGE_ME"
EOF
sudo chmod 600 /etc/kestra-restic.env
Inicialice el repositorio una vez y después añada la carga de datos al final del script de backup. Antes de utilizarlo, compruebe los parámetros de endpoint y bucket del proveedor S3 elegido.
source /etc/kestra-restic.env
restic init
restic backup /opt/kestra/backups
restic snapshots
Añada una ejecución diaria. Cron se ejecuta con el usuario ops, por lo que debe tener acceso a Docker y a los archivos del proyecto.
crontab -e
15 2 /opt/kestra/backup-kestra.sh >> /opt/kestra/backups/backup.log 2>&1
30 2 . /etc/kestra-restic.env && restic backup /opt/kestra/backups && restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune >> /opt/kestra/backups/restic.log 2>&1
Actualización de Kestra sin pérdida de datos
Para un VPS, la actualización es una breve maintenance window, no un rolling deployment real. Primero haga un backup, lea las release notes de la versión elegida, actualice la etiqueta de la imagen en compose.yml, descargue la imagen y reinicie solo el contenedor de Kestra.
cd /opt/kestra
./backup-kestra.sh
docker compose pull kestra
docker compose up -d --no-deps kestra
docker compose logs -f --tail=100 kestra
Después de la actualización, compruebe la interfaz, la ejecución de un workflow de prueba, la programación y las migraciones de la base de datos. No elimine inmediatamente la imagen antigua: puede ser útil para revertir los cambios si la nueva versión es incompatible con el plugin utilizado. Antes de actualizar la major-version de PostgreSQL, realice un backup independiente y verificado y siga el procedimiento de upgrade de PostgreSQL, en lugar de limitarse a cambiar la etiqueta del contenedor.
Solución de problemas + FAQ
¿Por qué Caddy no obtiene el certificado TLS y aparece un error ACME en los logs?
Primero compruebe el registro A del dominio con el comando getent ahostsv4 kestra.example.com: debe devolver la IP del VPS. Después asegúrese de que UFW permite los puertos TCP 80 y 443 y de que el proveedor no tiene un cloud firewall adicional. Compruebe que otro servidor web no haya ocupado el puerto 80: sudo ss -ltnp | grep ':80\|:443'. Después de corregir el DNS y los puertos, ejecute sudo systemctl reload caddy y revise journalctl -u caddy.
¿Por qué aparece 502 Bad Gateway en el navegador?
El código 502 significa que Caddy está disponible, pero no puede conectarse a Kestra. Ejecute docker compose ps en el directorio /opt/kestra: el contenedor de Kestra debe estar en estado running o healthy. Después compruebe localmente curl -I http://127.0.0.1:8080/. Si no hay respuesta, revise docker compose logs kestra y docker compose logs postgres. Una causa frecuente es que PostgreSQL aún no haya superado el healthcheck o que se hayan indicado variables de base de datos incorrectas.
Kestra se reinicia constantemente. ¿Qué hay que comprobar?
Comience con docker compose logs --tail=200 kestra. Los errores de conexión a PostgreSQL suelen indicar una contraseña, un nombre de base de datos o una configuración JDBC incorrectos. Los errores OutOfMemoryError significan que al VPS le falta RAM o que el parámetro -Xmx es demasiado grande. Compruebe la memoria mediante free -h y docker stats. En un VPS con 2 GB de RAM es mejor no ejecutar PostgreSQL y Kestra con workflows pesados simultáneamente.
¿Por qué no se inicia el workflow mediante cron?
Compruebe que el flow esté guardado, habilitado y no contenga errores en YAML. En cron, en una expresión de cinco campos, /15 * significa cada 15 minutos. Asegúrese de que la timezone esté indicada explícitamente: sin ella, la hora puede interpretarse en UTC. Abra la sección Executions y busque errores del trigger. Consulte también los logs: docker compose logs kestra | grep -i schedule. Para realizar una prueba, establezca temporalmente una programación cada minuto.
¿Qué configuración de VPS es mínimamente adecuada?
Para familiarizarse con Kestra puede utilizar 1 vCPU, 2 GB de RAM y 25 GB de NVMe, pero no es una opción de production cómoda. El mínimo práctico para Kestra, PostgreSQL, Caddy y varios workflows pequeños es 2 vCPU, 4 GB de RAM y 40–60 GB de NVMe. Si los workflows ejecutan Python, Chromium, Docker-containers o procesan archivos, elija como mínimo 4 vCPU y 8 GB de RAM.
¿Qué elegir: VPS o dedicated para esta tarea?
Un VPS es suficiente para la automatización de API, tareas SQL, informes periódicos, webhook y la mayoría de los procesos ETL pequeños. Se necesita un dedicated con una carga de CPU pesada constante, decenas de container jobs paralelos, grandes volúmenes de datos locales o un requisito estricto de recursos dedicados. Normalmente es más razonable comenzar con un VPS y observar docker stats, la carga de CPU, los IOPS y el tiempo de ejecución de los workflows.
¿Se puede abrir PostgreSQL al exterior para conectarse desde DBeaver?
Técnicamente es posible, pero normalmente no es necesario ni seguro para Kestra. PostgreSQL debe permanecer dentro de la red de Docker. Para el acceso administrativo, utilice un túnel SSH: ssh -L 5432:127.0.0.1:5432 ops@SERVER_IP, aunque para ello será necesario reenviar temporalmente PostgreSQL solo a localhost. Una opción más segura es ejecutar los comandos SQL mediante docker compose exec postgres psql directamente en el servidor.
¿Por qué el backup no encuentra el Docker volume de Kestra?
Primero ejecute docker volume ls y busque el volume asociado con storage. Docker Compose forma el nombre a partir del project name y del nombre del volume, por lo que puede diferir del ejemplo si el proyecto se inició desde otro directorio o con el parámetro -p. Corrija el nombre del volume en el script de backup. Compruebe también los permisos del directorio /opt/kestra/backups y asegúrese de que cron se ejecute con un usuario perteneciente al grupo docker.
Conclusiones y próximos pasos
Ahora Kestra funciona en el VPS con PostgreSQL, Docker Compose, HTTPS mediante Caddy, protección básica del servidor, programaciones de cron y copias de seguridad. La instancia está lista para ejecutar production-workflows si comprueba periódicamente los logs, el espacio libre en el disco y el éxito del backup.
- Cree namespaces independientes para desarrollo y production, y traslade los secretos a Kestra Secrets o a un secret manager externo.
- Configure notificaciones en Telegram, Slack o email para las executions con estado
FAILED. - Cuando aumente la carga, traslade storage a un almacenamiento compatible con S3, PostgreSQL a un servidor independiente o a una base de datos gestionada, y aísle la carga de los workers en contenedores independientes.
Antes de cualquier actualización de Kestra, realice un backup verificable y pruebe manualmente los workflows críticos. Esto es más importante que la actualización automática de imágenes y permite revertir rápidamente los cambios si hay incompatibilidades de plugins o cambios en la configuración.