Coolify a fondo: tu propia plataforma de despliegue en tu servidor
Arquitectura, despliegues sin cortes, variables, dominios, bases de datos, copias, CI y seguridad: cómo usar Coolify en serio y no llevarte sustos.
Contenido
Las plataformas como Heroku, Vercel o Netlify nos malacostumbraron. Haces git push y, un minuto después, tu aplicación está en Internet con su dominio y su certificado. Es magia, hasta que llega la factura, te topas con un límite del plan gratuito o necesitas una base de datos que no está en el catálogo.
La alternativa clásica era alquilar un servidor y montarlo todo a mano: Nginx, certificados, Docker, scripts de despliegue, copias de seguridad… Funciona, pero cada proyecto nuevo te cuesta una tarde y cada servidor acaba siendo un copo de nieve que nadie se atreve a tocar.
Coolify está justo en medio: la comodidad de una plataforma en la nube, pero en tu servidor. En el mío conviven varias aplicaciones, un entorno de pruebas protegido con contraseña y este mismo blog. En este artículo no me voy a quedar en el "instala y pulsa Deploy": quiero que entiendas qué pasa por debajo, porque es lo que te salvará el día que algo no arranque.
Qué es Coolify
Coolify es una plataforma como servicio (PaaS) de código abierto y autoalojable, creada por Andras Bacsai. Lo instalas en un servidor Linux y te da un panel web y una API desde los que despliegas tres tipos de cosas:
- Aplicaciones desde un repositorio Git o una imagen de Docker: Node, Python, PHP, Go, Rust, sitios estáticos…
- Bases de datos: PostgreSQL, MySQL, MariaDB, MongoDB, Redis y otras, con copias de seguridad programadas.
- Servicios de un clic: cientos de plantillas listas, como Ghost, n8n, Plausible, Uptime Kuma, Supabase o Nextcloud.
Es gratis si lo alojas tú. También existe Coolify Cloud, de pago, donde ellos mantienen el panel y tú solo conectas tus servidores.
La arquitectura: qué hay dentro
Todo en Coolify son contenedores Docker, incluido el propio Coolify. Este es el mapa:

coolify-proxy: un Traefik (o Caddy, si lo cambias) que escucha en los puertos 80 y 443. Recibe todas las peticiones, mira el dominio, las manda al contenedor correcto y se encarga de los certificados de Let's Encrypt.coolify: el panel, la API y la cola que ejecuta los despliegues.coolify-db,coolify-redisycoolify-realtime: la base de datos de Coolify, las colas y los websockets que actualizan el panel en directo.- Tus recursos: cada aplicación, base de datos o servicio es uno o varios contenedores con su propia red, sus volúmenes y sus variables.
La configuración y los datos de Coolify viven en /data/coolify. Tus recursos guardan sus datos en volúmenes de Docker, así que un despliegue nuevo no borra la base de datos.
Los conceptos del panel
- Proyecto: agrupa todo lo de una aplicación (por ejemplo, "blog").
- Entorno: dentro de un proyecto,
production,staging… Cada uno con sus propios recursos y variables. - Recurso: una aplicación, una base de datos o un servicio.
- Servidor: la máquina donde se ejecuta. Puede ser la misma de Coolify u otra conectada por SSH.
- Destino: la red de Docker del servidor donde se conectan los recursos.
Instalarlo bien desde el principio
Requisitos
- Un servidor Linux limpio, por ejemplo un VPS con Ubuntu LTS, dedicado a esto.
- Mínimo 2 núcleos, 2 GB de RAM y 30 GB de disco. Para varias aplicaciones y bases de datos, cuenta con 4 u 8 GB: las compilaciones son lo que más memoria gasta.
- Acceso SSH como root o con sudo, y un dominio.
El instalador
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | sudo bashInstala Docker si hace falta, crea /data/coolify, genera las claves y arranca los contenedores. En un par de minutos tienes el panel en http://IP-DE-TU-SERVIDOR:8000.
Dominio para el panel y DNS
En tu proveedor de DNS crea un registro A para el panel (por ejemplo coolify.tudominio.com) apuntando a la IP del servidor, y ponlo en Settings como dominio de la instancia. Coolify pedirá el certificado y podrás olvidarte del puerto 8000.
Un truco muy cómodo: crea también un registro comodín (*.apps.tudominio.com) y configúralo como Wildcard Domain del servidor. Cada aplicación nueva recibirá automáticamente un subdominio con HTTPS.
Cortafuegos: la trampa de Docker
Cuando todo funcione con dominio, cierra los puertos que no necesitas: el 8000 del panel y los de los websockets (6001 y 6002). Desde fuera solo deberían verse el 22, el 80 y el 443.
# Solo SSH, HTTP y HTTPS desde fuera
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enableufw. Cuando un contenedor publica un puerto, Docker escribe sus propias reglas de iptables, que se evalúan antes que las de ufw. Un puerto publicado queda abierto a Internet aunque ufw diga lo contrario. La solución más sencilla es usar el cortafuegos de tu proveedor (Hetzner, DigitalOcean… lo tienen gratis) y no publicar puertos de bases de datos.Y ya que estás: entra por SSH solo con clave, nunca con contraseña, y activa la verificación en dos pasos en el panel.
Cómo funciona un despliegue
Cuando despliegas una aplicación, Coolify sigue siempre los mismos pasos:

- Descarga el código de tu repositorio (o la imagen, si despliegas una imagen ya hecha).
- Arranca un contenedor auxiliar que construye la imagen de Docker.
- Arranca un contenedor nuevo con esa imagen, sin parar el viejo.
- Espera a que el health check diga que la aplicación responde.
- Cambia el tráfico al contenedor nuevo y retira el viejo.
Esto es un despliegue sin cortes (rolling update). Para que funcione, tu aplicación necesita un health check que funcione, y no puede tener un nombre de contenedor fijo ni puertos publicados a mano en el servidor. Si no se cumplen, Coolify para el viejo y arranca el nuevo, y tendrás unos segundos de caída en cada despliegue.
Los build packs
Es la forma en la que Coolify construye tu imagen. Tienes cinco:
| Build pack | Cuándo usarlo |
|---|---|
| Nixpacks | Proyectos sencillos sin Dockerfile. Detecta el lenguaje y lo construye solo. |
| Dockerfile | Cuando quieres control total. Mi opción por defecto. |
| Docker Compose | Aplicaciones con varios contenedores (app + base de datos + worker). |
| Static | Sitios estáticos ya compilados: se sirven con un servidor web ligero. |
| Docker Image | La imagen ya está construida en un registro (por ejemplo, desde tu CI). |
Con Nixpacks puedes afinar la detección con un nixpacks.toml, por ejemplo para añadir paquetes del sistema:
# nixpacks.toml en la raíz del repositorio
[phases.setup]
nixPkgs = ["nodejs_22", "ffmpeg"]
[phases.build]
cmds = ["npm run build"]
[start]
cmd = "node dist/server.js"Con Dockerfile, usa construcción en varias etapas para que la imagen final sea pequeña y no lleve herramientas de compilación:
# --- Etapa 1: dependencias y build ---
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# --- Etapa 2: imagen final, pequeña ---
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
# wget ya viene en Alpine; lo usa el health check
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]El health check
En la configuración de la aplicación indicas una ruta (por ejemplo /health) y un puerto. Coolify lo comprueba desde dentro del contenedor, así que tu imagen necesita curl o wget. Las imágenes Alpine traen wget; algunas imágenes mínimas no traen ninguno de los dos, y entonces el health check falla aunque la aplicación funcione perfectamente.
El error más común: escuchar en 127.0.0.1
Si tu aplicación arranca pero ves un Bad Gateway, casi seguro que escucha solo en 127.0.0.1. Dentro de un contenedor, esa dirección es el propio contenedor, y el proxy no puede llegar a ella:
// Mal: solo acepta conexiones desde dentro del propio contenedor
app.listen(3000, '127.0.0.1');
// Bien: acepta conexiones del proxy
app.listen(process.env.PORT || 3000, '0.0.0.0');Variables de entorno y secretos
Cada recurso tiene su pestaña de variables. Tres cosas importantes:
- Variables de build y de ejecución: algunas solo hacen falta al construir (por ejemplo, la URL pública que se incrusta en un frontend) y otras solo al ejecutar (la contraseña de la base de datos). Márcalas según corresponda para no meter secretos en la imagen.
- Variables compartidas: puedes definir variables a nivel de equipo, proyecto o entorno y usarlas en varios recursos con la sintaxis
{{project.NOMBRE}}o{{environment.NOMBRE}}. - Nunca en el repositorio: los
.envcon secretos no se suben a Git. Van en el panel.
Las variables "mágicas" de Docker Compose
En las aplicaciones y servicios con Docker Compose, Coolify entiende unas variables especiales y las rellena por ti:
SERVICE_FQDN_APP_3000: genera un dominio para el servicioappy lo enruta a su puerto 3000.SERVICE_URL_APP: la URL completa de ese servicio.SERVICE_USER_XySERVICE_PASSWORD_X: un usuario y una contraseña aleatorios que se generan una vez y se guardan.
services:
app:
image: ghcr.io/tu-usuario/mi-app:latest
environment:
# Coolify genera el dominio y lo enruta al puerto 3000
- SERVICE_FQDN_APP_3000
- DATABASE_URL=postgres://${SERVICE_USER_POSTGRES}:${SERVICE_PASSWORD_POSTGRES}@db:5432/app
depends_on:
- db
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
interval: 10s
retries: 5
db:
image: postgres:17-alpine
environment:
- POSTGRES_USER=${SERVICE_USER_POSTGRES}
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES}
- POSTGRES_DB=app
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:En mi experiencia, estas variables funcionan mejor escritas en el propio compose que añadidas después desde el panel. Si alguna no se rellena, revisa la documentación de tu versión: es una de las zonas que más cambia.
Dominios, HTTPS y el proxy
- Un recurso puede tener varios dominios, separados por comas. Coolify pide un certificado para cada uno.
- Puedes redirigir
wwwal dominio sinwww(o al revés) desde la configuración del recurso. - Para casos especiales, puedes editar las etiquetas de Traefik del recurso: cabeceras, redirecciones, límites o autenticación.
Proteger un entorno de pruebas con contraseña
Para un entorno de pruebas que no quieres público, basta con una autenticación básica en el proxy. Generas el usuario y la contraseña cifrada:
# Genera usuario y contraseña cifrada (paquete apache2-utils)
htpasswd -nbB tester 'una-contraseña-larga'
# tester:$2y$05$Kq3...
# En las etiquetas de Docker, cada $ se escribe como $$y añades un middleware basicauth de Traefik con ese valor a las etiquetas del recurso, enganchado al router que genera Coolify. Es rápido y el navegador pedirá usuario y contraseña antes de mostrar nada.
Cloudflare delante
Si tu dominio está en Cloudflare, tienes dos opciones:
- Nube gris (solo DNS): Cloudflare solo resuelve el nombre y los visitantes llegan directos a tu servidor. Es lo más sencillo y Let's Encrypt funciona sin problemas.
- Nube naranja (proxy): Cloudflare se pone en medio. Pon el modo SSL en Full (strict). Con el modo Flexible, Cloudflare le habla a tu servidor por HTTP, Traefik le redirige a HTTPS, y el navegador acaba mostrando
ERR_TOO_MANY_REDIRECTS. Me pasó con este blog.
Bases de datos y copias de seguridad
Crear una base de datos es un clic. Coolify te da dos direcciones: la interna, para que la usen tus aplicaciones dentro de la red de Docker, y la opción de hacerla pública con un puerto. Usa siempre la interna; si necesitas conectarte desde tu ordenador, hazlo con un túnel SSH en lugar de abrir el puerto.
En la pestaña de copias de seguridad de cada base de datos programas copias con una expresión cron (por ejemplo 0 3 * * *, todos los días a las tres) y eliges cuántas conservar. Lo importante es enviarlas fuera del servidor: Coolify admite cualquier almacenamiento compatible con S3, como Cloudflare R2, Backblaze B2 o un MinIO en otra máquina.
# Copia del volumen de contenido de un servicio (por ejemplo, Ghost)
docker run --rm \
-v NOMBRE_DEL_VOLUMEN:/datos:ro \
-v /root/copias:/copias \
alpine tar czf /copias/contenido-$(date +%F).tar.gz -C /datos .Y como siempre: prueba a restaurar. Una vez al trimestre, levanta la copia en un entorno de pruebas y comprueba que está completa.
Despliegues automáticos y CI
Si conectas tu cuenta de GitHub mediante la GitHub App de Coolify, cada push a la rama configurada despliega automáticamente. También puedes activar los despliegues de vista previa: cada pull request recibe su propio entorno temporal con su URL.
Si prefieres que los tests pasen antes de desplegar, desactiva el despliegue automático y llama a la API de Coolify desde tu CI. Crea un token en Keys & Tokens y guarda el token y el identificador de la aplicación como secretos del repositorio:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
test-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci && npm test
- name: Desplegar en Coolify
run: |
curl --fail -s \
-H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" \
"https://coolify.tudominio.com/api/v1/deploy?uuid=${{ secrets.COOLIFY_APP_UUID }}&force=false"Varios servidores
Coolify puede gestionar más máquinas: le das una clave SSH, la añades en Servers, y a partir de ahí despliegas en ella como en la principal. Algunas ideas:
- Separar el panel de las aplicaciones: si el servidor de las aplicaciones se satura, el panel sigue respondiendo.
- Un servidor de build: las compilaciones consumen mucha CPU y memoria. Puedes hacerlas en otra máquina y que el servidor de producción solo ejecute.
- Producción y pruebas separadas: cada una en su servidor, desde el mismo panel.
Recursos, espacio y actualizaciones
- Limita la memoria de cada recurso en su configuración avanzada. En un servidor pequeño, una aplicación con una fuga de memoria puede tumbar a todas las demás, incluido Coolify.
- Vigila el disco: cada build deja imágenes y caché. Coolify limpia automáticamente cuando el disco pasa de un umbral que puedes ajustar en la configuración del servidor.
- Notificaciones: configura avisos por correo, Discord o Telegram para despliegues fallidos, copias de seguridad y servidores caídos.
- Actualiza con calma: Coolify avanza muy rápido. Puedes desactivar las actualizaciones automáticas y actualizar tú, después de leer las notas de la versión.
Estos comandos te sacan de más de un apuro cuando algo no cuadra:
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
docker logs --tail 100 NOMBRE_DEL_CONTENEDOR
docker stats --no-stream # CPU y memoria de cada contenedor
docker system df # cuánto ocupan imágenes, volúmenes y cachéProblemas comunes y su causa
| Síntoma | Causa más probable |
|---|---|
| 404 page not found (de Traefik) | El dominio no coincide con el configurado en el recurso, o el contenedor no está en marcha. |
| Bad Gateway | La app escucha en 127.0.0.1 o en otro puerto del indicado. |
| ERR_TOO_MANY_REDIRECTS | Cloudflare en modo SSL Flexible con la nube naranja. |
| El certificado no se emite | El DNS aún no apunta al servidor, el puerto 80 está cerrado o Cloudflare interfiere. |
| El build muere sin error claro | Falta memoria. Añade swap, sube de servidor o usa un servidor de build. |
| El health check falla siempre | La imagen no tiene curl ni wget, o la ruta no existe. |
| Los datos desaparecen al redesplegar | La app guarda archivos dentro del contenedor en lugar de en un volumen. |
En resumen
Coolify me ha devuelto el gusto por desplegar cosas. Un servidor, un panel cómodo y la tranquilidad de que todo es mío: el código, los datos y la factura. Pero no es magia: por debajo hay Docker, un proxy y unos cuantos contenedores, y entender esas piezas es lo que marca la diferencia entre "funciona" y "funciona y sé por qué".
Si te quedas con cinco cosas: crea la cuenta de administrador nada más instalar, cierra puertos con el cortafuegos del proveedor, haz que tu app escuche en 0.0.0.0 con un health check, limita la memoria y saca las copias del servidor. Con eso, tendrás tu propio Heroku sin sustos.
Este blog, sin ir más lejos, vive ahí.