Deployment en un VPS con Coolify
vps-coolify-deployment.Deployar a tu propio VPS con Coolify te da más control y evita las limitaciones de cold starts de los tiers gratuitos de PaaS. Sin embargo, este documento no va a entrar demasiado en detalle sobre cómo configurar un VPS + dominio + instancia de Coolify en sí.
Para eso, te recomiendo chequear:
- Mi serie personal de blog sobre VPS setup, donde abordo ese desafío paso a paso
- Dreams of Code: "Setting up a production ready VPS is a lot easier than I thought"
Una vez que tenés tu instancia de Coolify up and running, deployemos tu aplicación Spring Boot.
¿Por qué un VPS?
Hay un montón de formas de hostear una aplicación web en 2026. Clusters de Kubernetes manejados, funciones serverless, proveedores PaaS con tiers gratuitos generosos, lo que se te ocurra. Acá te dejo un mapa aproximado del panorama:
Para este proyecto, vamos con un VPS en el cuadrante "Developer Cloud". La razón es simple: es lo más fácil de explicar, y lo más fácil de razonar. Tenés una caja Linux, te conectás por SSH, corrés tus containers. No hay una capa de abstracción escondiendo lo que pasa.
Esa simplicidad viene con tradeoffs. Al elegir un solo VPS, estás resignando cosas que los sistemas de producción del mundo real suelen necesitar:
- Load balancing entre múltiples instancias
- Rolling deployments con zero downtime
- Auto-scaling cuando el tráfico se dispara
- Multi-region para redundancia y disponibilidad global
- Bases de datos manejadas con backups automáticos y failover
No necesitás nada de eso ahora. Estás aprendiendo cómo funciona el deployment, no arquitectando para millones de usuarios. Un solo VPS con Docker Compose es más que suficiente, y cuando llegue el momento de escalar, vas a entender los fundamentos lo suficientemente bien como para saber de qué estás escalando.
Visión general de la arquitectura
Acá te muestro exactamente qué pasa cuando hacés push de código a la branch main:
La idea clave acá es que Coolify solo deploya si GitHub Actions pasa. Esto previene que código roto llegue a producción. GitHub Actions maneja el heavy lifting de compilar y testear a través de las tres implementaciones de lenguaje, mientras Coolify se enfoca puramente en la orquestación del deployment.
Alternativa: enfoque con Docker registry
La arquitectura de arriba hace que Coolify buildee las imágenes Docker en el VPS mismo. Hay otro patrón común donde GitHub Actions buildea las imágenes, las pushea a un container registry (como GitHub Container Registry, Docker Hub, o AWS ECR), y Coolify solo descarga las imágenes pre-buildeadas:
Ambos enfoques son válidos. Acá te muestro cómo se comparan:
| Coolify buildea (esta guía) | Basado en registry | |
|---|---|---|
| Uso de recursos del VPS | Mayor: la compilación ocurre en tu servidor | Menor: tu VPS solo descarga y ejecuta imágenes |
| Minutos de CI | Menos: CI solo corre tests | Más: CI también buildea y pushea imágenes Docker |
| Complejidad del pipeline | Más simple: menos partes móviles, sin credenciales de registry que manejar | Más setup: auth del registry, estrategia de tagging de imágenes, políticas de limpieza |
| Consistencia del build | Los builds ocurren en el hardware del VPS, que puede diferir del CI | Las imágenes son idénticas en todos lados, buildeadas una vez en un entorno controlado |
| Velocidad de deployment | Más lento: Coolify compila desde el código fuente cada vez | Más rápido: Coolify solo descarga una imagen lista para correr |
| Rollbacks | Requiere un rebuild desde un commit anterior | Descargás un tag de imagen anterior, casi instantáneo |
| Debugging de builds | Más difícil: los logs de build están en Coolify, separados del CI | Más fácil: todo está en un solo lugar (GitHub Actions) |
Si tu VPS tiene recursos limitados (1-2 GB de RAM), buildear aplicaciones Java con Gradle en él puede ser doloroso. Mover el build a GitHub Actions (que tiene 7 GB de RAM en runners gratuitos) y pushear imágenes pre-buildeadas a un registry es una solución práctica.
Esta guía usa el enfoque de "Coolify buildea" porque es más simple de configurar y no requiere manejar credenciales de registry ni políticas de retención de imágenes. Para un proyecto chico o un setup de aprendizaje, la menor complejidad vale la pena el tradeoff.
Configuración del repositorio
Esta guía demuestra deployar un monorepo que contiene tres proyectos Spring Boot (implementaciones Java, Kotlin y Groovy de la misma API). Si bien la configuración es más compleja que un repo de proyecto único, los conceptos principales se mantienen. Si estás trabajando con un repositorio más simple de proyecto único, vas a saltear la configuración de Docker Compose y deployar un solo servicio.
Antes de configurar Coolify, necesitás preparar tu repositorio con configuraciones de Docker y CI/CD. Acá te muestro un resumen de los archivos nuevos y modificados:
docker-compose.yml
Este archivo orquesta los tres servicios Spring Boot:
Cada servicio:
- Build desde su propio Dockerfile ubicado en el respectivo directorio del módulo
- Mapea el puerto 8080 al puerto interno de Spring Boot (8080). El puerto externo coincide porque Coolify maneja el routing
- Activa el profile
prodpara settings optimizados de producción - Incluye un healthcheck usando Spring Boot Actuator para asegurar que el contenedor esté realmente listo
- Restart automático si el contenedor crashea
Dockerfiles
Cada módulo obtiene su propio Dockerfile optimizado:
- Java
- Kotlin
- Groovy
Algunos detalles importantes sobre este build multi-stage:
- Stage 1 (Build): Usa la imagen full de Gradle con JDK 21 para compilar la aplicación. Saltamos tests y formateo de Spotless porque esos ya corrieron en GitHub Actions.
settings-docker.gradletrick: Cada módulo tiene un archivo de settings mínimo que solo se incluye a sí mismo. Esto previene que Docker intente buildear el monorepo entero cuando solo queremos un servicio.- Stage 2 (Runtime): Usa una imagen slim solo JRE de Alpine para una superficie de ataque más pequeña y deployments más rápidos.
curlinstallation: Requerido para que funcione el healthcheck.
settings-docker.gradle
Cada módulo necesita un archivo de settings standalone para builds de Docker:
- Java
- Kotlin
- Groovy
Esta configuración mínima le dice a Gradle que solo buildee el módulo específico, evitando compilación innecesaria del monorepo entero durante la fase de build de Docker.
Excepción de Gradle Wrapper en .gitignore
gradle/wrapper/gradle-wrapper.jar actualmente es necesario para el deployment con Docker Compose porque:
- Configuración actual de Docker: Cada Dockerfile usa
gradlewpara buildear los JARs durante el proceso de build de Docker. - Builds multi-stage: La etapa de build depende de Gradle para compilar y empaquetar las aplicaciones.
- Personalización de settings: Los Dockerfiles usan archivos de Gradle settings personalizados.
Excluilo de ser ignorado:
.github/workflows/ci-cd.yml
El workflow de GitHub Actions orquesta el pipeline CI/CD:
Este workflow tiene dos jobs:
build-and-test: Corre en cada push y pull request. Compila los tres módulos, corre tests y sube reportes de test como artifacts.deploy: Solo corre en pushes amaindespués de quebuild-and-testtenga éxito. Dispara el webhook de deployment de Coolify.
El workflow usa dos secrets (COOLIFY_DEPLOY_UUID y COOLIFY_API_TOKEN) que vas a configurar después en GitHub.
Configuración de Coolify + GitHub Actions
Ahora que tu repositorio está listo, configuremos Coolify para que trabaje con GitHub Actions.
Paso 1: Crear el recurso de Coolify
-
Andá a tu proyecto en Coolify y clickeá + Add Resource.
-
Bajo Git Based, seleccioná Private Repository (with GitHub App).
-
Seleccioná tu GitHub App conectada.
-
Configurá la aplicación:
- Repository: Seleccioná tu repositorio (ej.
springboot-demo-projects) - Branch:
main - Build Pack: Docker Compose
- Base Directory:
/ - Docker Compose Location:
/docker-compose.yml
La Extensión del Archivo ImportaAsegurate de que la ubicación de Docker Compose coincida con tu extensión de archivo real. Es fácil confundir
.ymly.yaml. Coolify va a fallar en encontrar el archivo si no coinciden exactamente.
- Repository: Seleccioná tu repositorio (ej.
Paso 2: Configurar los settings generales
Una vez creada, andá a la pestaña Configuration y seteá tus servicios:
-
Domains: Agregá tus dominios custom:
https://sakila-java.your-domain.com:8080https://sakila-kotlin.your-domain.com:8080https://sakila-groovy.your-domain.com:8080
Configuración de PuertosEsto es crucial: Cada servicio está configurado para exponer el puerto 8080. El reverse proxy de Coolify va a manejar el routing de tráfico a cada contenedor basado en el nombre de dominio. Si encontrás problemas de routing, chequeá la documentación de Coolify para más detalles.
-
Docker Compose Editor: Verificá que el contenido de tu
docker-compose.ymlse muestre correctamente.
Paso 3: Deshabilitar Auto Deploy
Navegá a la pestaña Advanced y desmarcá Auto Deploy.
Crítico: Debés deshabilitar Auto Deploy. Si se deja habilitado, Coolify va a deployar en cada git push, bypassing tu pipeline CI de GitHub Actions. Esto derrota el propósito de tener tests como gate de tus deployments.
Paso 4: Obtener el Deploy Webhook URL
Navegá a la pestaña Webhooks para encontrar tu deployment trigger URL:
Copiá el Deploy Webhook URL. Se ve así:
https://coolify.your-domain.com/api/v1/deploy?uuid=YOUR_UUID&force=false
El parámetro de query uuid es el identificador único para tu recurso de Coolify. Lo vas a guardar en GitHub como el secret COOLIFY_DEPLOY_UUID.
Paso 5: Generar API Token
-
Andá a Keys & Tokens → API Tokens en la sidebar izquierda.
-
Si la API está deshabilitada, habilitala en Settings primero.
-
Creá un nuevo token:
- Description:
github-actions - Permissions: Solo marcar
deploy
Permisos MínimosSolo otorgá el permiso
deploy. El workflow de GitHub Actions solo necesita disparar deployments, no leer datos sensibles o modificar recursos.
- Description:
-
Copiá el token generado. Lo vas a guardar en GitHub como el secret
COOLIFY_API_TOKEN.
Paso 6: Configurar los secrets de GitHub
Andá a tu repositorio de GitHub → Settings → Secrets and variables → Actions, y agregá estos repository secrets:
-
COOLIFY_API_TOKEN: El token de API que acabás de generar -
COOLIFY_DEPLOY_UUID: El UUID del webhook URL (el valor del parámetro de queryuuid)
Verificación
Hacé push de tus cambios a la branch main y mirá la magia suceder:
-
Chequeá la ejecución de GitHub Actions para confirmar que el pipeline CI funciona:
- Tu workflow debería dispararse y mostrar ambos jobs
build-and-testydeploy - Buscá el checkmark verde indicando éxito
- Tu workflow debería dispararse y mostrar ambos jobs
-
Después de un deployment exitoso (usualmente 3-5 minutos), lo vas a ver en la pestaña Deployments:
-
Finalmente, testeá tu API deployada:
¡Felicitaciones! Configuraste exitosamente un pipeline CI/CD listo para producción para tu monorepo Spring Boot. Ahora cada push a main va a correr tests automáticamente antes de deployar, dándote confianza de que producción siempre esté en un estado funcional.