Cachet es una página de estado de código abierto escrita en PHP con Laravel. Permite mostrar el estado de tus servicios agrupados en componentes, publicar incidentes y mantenimientos programados, representar métricas y avisar a los suscriptores por correo. En este tutorial desplegarás Cachet con Docker Compose y PostgreSQL en Ubuntu 24.04, lo publicarás en tu dominio con Nginx y HTTPS, y usarás su API para crear componentes, abrir y resolver incidentes y enviar métricas de forma automática.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM.
  • Un usuario no root con privilegios sudo.
  • Docker Engine y el plugin Docker Compose instalados desde el repositorio oficial.
  • Un subdominio, por ejemplo status.your_domain, con un registro DNS A apuntando a la IP del servidor.
  • Los puertos 80 y 443 abiertos.
  • Opcional: los datos de un servidor SMTP para enviar notificaciones a los suscriptores.

Conviene alojar la página de estado en un servidor distinto al de los servicios que monitoriza. Si ese servidor cae, la página de estado debe seguir disponible para informar.

Paso 1: Preparar el directorio y los secretos

Crea un directorio para el proyecto:

sudo mkdir -p /opt/cachet
sudo chown "$USER":"$USER" /opt/cachet
cd /opt/cachet

Cachet necesita una clave de aplicación de Laravel (APP_KEY), que cifra sesiones y datos, y la base de datos necesita una contraseña. Genera ambas con openssl y guárdalas en un archivo .env que Docker Compose leerá automáticamente:

cat > .env <<EOF
APP_KEY=base64:$(openssl rand -base64 32)
DB_PASSWORD=$(openssl rand -hex 24)
EOF
chmod 600 .env
cat .env
APP_KEY=base64:kR3v1pQ...=
DB_PASSWORD=9f2c4e...

Guarda una copia de APP_KEY en un lugar seguro: si la pierdes, Cachet no podrá descifrar los datos existentes.

Paso 2: Crear el archivo de Docker Compose

Crea compose.yaml con dos servicios: PostgreSQL para los datos y Cachet, que escucha en el puerto 8000 del contenedor:

nano /opt/cachet/compose.yaml
services:
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: cachet
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: cachet
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U cachet"]
      interval: 10s
      timeout: 5s
      retries: 5

  cachet:
    image: cachethq/docker:latest
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      DB_DRIVER: pgsql
      DB_HOST: postgres
      DB_PORT: "5432"
      DB_DATABASE: cachet
      DB_USERNAME: cachet
      DB_PASSWORD: ${DB_PASSWORD}
      DB_PREFIX: chq_
      APP_KEY: ${APP_KEY}
      APP_ENV: production
      APP_DEBUG: "false"
      APP_LOG: errorlog
      DEBUG: "false"
      MAIL_DRIVER: smtp
      MAIL_HOST: smtp.your_domain
      MAIL_PORT: "587"
      MAIL_USERNAME: status@your_domain
      MAIL_PASSWORD: your_smtp_password
      MAIL_ADDRESS: status@your_domain
      MAIL_NAME: "Estado de servicios"
      MAIL_ENCRYPTION: tls

volumes:
  pgdata:

Detalles importantes:

  • Cachet se publica solo en 127.0.0.1:8000. Nginx será el único punto de entrada público. Docker gestiona sus propias reglas de iptables, así que un puerto publicado en todas las interfaces quedaría abierto aunque UFW lo bloquee.
  • Las variables MAIL_* configuran el envío de correo. Sustituye los valores de ejemplo por los de tu proveedor SMTP o elimina ese bloque si no vas a usar suscriptores.
  • Las contraseñas se leen del archivo .env, no se escriben en el compose.yaml.

Paso 3: Arrancar Cachet

Descarga las imágenes y arranca los contenedores en segundo plano:

docker compose up -d

En el primer arranque, Cachet crea las tablas en PostgreSQL. Comprueba el estado de los servicios:

docker compose ps
NAME                IMAGE                    SERVICE    STATUS                    PORTS
cachet-cachet-1     cachethq/docker:latest   cachet     Up 40 seconds             127.0.0.1:8000->8000/tcp
cachet-postgres-1   postgres:16-alpine       postgres   Up 51 seconds (healthy)

Verifica que la API responde con el endpoint ping, que no requiere autenticación:

curl -s http://127.0.0.1:8000/api/v1/ping
{"data":"Pong!"}

Si no obtienes respuesta, revisa los logs con docker compose logs --tail 50 cachet.

Paso 4: Publicar Cachet con Nginx y HTTPS

Instala Nginx y Certbot y permite el tráfico web en UFW:

sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
sudo ufw allow 'Nginx Full'

Crea el bloque de servidor, sustituyendo status.your_domain por tu subdominio:

sudo nano /etc/nginx/sites-available/cachet
server {
    listen 80;
    listen [::]:80;
    server_name status.your_domain;

    location / {
        proxy_pass http://127.0.0.1:8000;
        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;
    }
}

Activa el sitio, valida la sintaxis y recarga Nginx:

sudo ln -s /etc/nginx/sites-available/cachet /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Solicita el certificado de Let's Encrypt. Certbot añadirá la configuración de TLS y la redirección de HTTP a HTTPS:

sudo certbot --nginx -d status.your_domain
Successfully deployed certificate for status.your_domain to /etc/nginx/sites-enabled/cachet
Congratulations! You have successfully enabled HTTPS on https://status.your_domain

Paso 5: Completar el asistente de instalación

Abre https://status.your_domain en el navegador. Cachet te redirige a /setup, un asistente de tres pantallas:

  1. Entorno: elige Database como controlador de caché y de sesiones, y SMTP como controlador de correo si has configurado las variables MAIL_*.
  2. Página de estado: nombre del sitio, dominio (https://status.your_domain), zona horaria e idioma. Selecciona Español si tus usuarios son hispanohablantes.
  3. Cuenta de administrador: nombre de usuario, correo y una contraseña robusta.

Al terminar, entra en https://status.your_domain/dashboard con esa cuenta. La página pública estará vacía hasta que crees componentes.

Paso 6: Obtener el token de la API

Todas las operaciones de escritura en la API v1 se autentican con la cabecera X-Cachet-Token. Cada usuario tiene su token en el panel: haz clic en tu nombre de usuario (perfil) y copia el valor del campo API Token.

Guárdalo en variables de tu sesión para los ejemplos siguientes:

export CACHET_URL="https://status.your_domain/api/v1"
export CACHET_TOKEN="your_api_token"

Comprueba que funciona listando los componentes:

curl -s "$CACHET_URL/components" -H "X-Cachet-Token: $CACHET_TOKEN"
{"meta":{"pagination":{"total":0,...}},"data":[]}

Paso 7: Crear grupos y componentes

Los componentes representan cada servicio que muestras (web, API, correo...) y los grupos los organizan en la página. Crea un grupo:

curl -s -X POST "$CACHET_URL/components/groups" \
  -H "X-Cachet-Token: $CACHET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Plataforma", "order": 1, "collapsed": 0}'

La respuesta incluye el id del grupo. Crea un componente dentro de él (sustituye 1 por el id devuelto):

curl -s -X POST "$CACHET_URL/components" \
  -H "X-Cachet-Token: $CACHET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "API", "description": "API REST pública", "status": 1, "group_id": 1, "enabled": true}'
{"data":{"id":1,"name":"API","description":"API REST pública","status":1,"status_name":"Operational",...}}

El campo status de un componente admite estos valores:

ValorEstado
1Operativo
2Problemas de rendimiento
3Interrupción parcial
4Interrupción grave

Para cambiar el estado de un componente existente, envía un PUT con el nuevo valor:

curl -s -X PUT "$CACHET_URL/components/1" \
  -H "X-Cachet-Token: $CACHET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": 2}'

Recarga la página pública: el componente API aparece dentro del grupo Plataforma con el estado actualizado.

Paso 8: Abrir y resolver un incidente

Un incidente informa a los usuarios de un problema y, opcionalmente, cambia a la vez el estado del componente afectado. Crea uno en estado "Investigando" que marque la API con problemas de rendimiento y avise a los suscriptores:

curl -s -X POST "$CACHET_URL/incidents" \
  -H "X-Cachet-Token: $CACHET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Latencia elevada en la API",
        "message": "Estamos investigando un aumento de la latencia en algunas peticiones.",
        "status": 1,
        "visible": 1,
        "component_id": 1,
        "component_status": 2,
        "notify": true
      }'

Los estados de un incidente son:

ValorEstado
0Programado (mantenimiento)
1Investigando
2Identificado
3En observación
4Resuelto

Cuando el problema esté solucionado, marca el incidente como resuelto y devuelve el componente a operativo (sustituye 1 por el id del incidente):

curl -s -X PUT "$CACHET_URL/incidents/1" \
  -H "X-Cachet-Token: $CACHET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": 4, "message": "El problema está resuelto. La latencia ha vuelto a valores normales.", "component_id": 1, "component_status": 1}'

Los incidentes también se pueden gestionar desde Dashboard > Incidentes, que es lo más cómodo para comunicaciones manuales. La API es útil para que tus herramientas de monitorización actualicen la página de forma automática.

Paso 9: Enviar métricas automáticamente

Cachet puede mostrar gráficas, por ejemplo el tiempo de respuesta de tu web. Crea la métrica en Dashboard > Métricas (nombre "Tiempo de respuesta", sufijo ms, cálculo por media) o con la API:

curl -s -X POST "$CACHET_URL/metrics" \
  -H "X-Cachet-Token: $CACHET_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Tiempo de respuesta", "suffix": "ms", "description": "Tiempo de respuesta de la web", "default_value": 0, "calc_type": 1, "display_chart": 1}'

Anota el id de la métrica. Ahora crea un script que mida el tiempo de respuesta y lo envíe como punto de la métrica. Guarda primero el token en un archivo solo legible por root, para no dejarlo en el script ni en el crontab:

sudo install -m 600 /dev/null /etc/cachet-metrics.env
sudo nano /etc/cachet-metrics.env
CACHET_URL=https://status.your_domain/api/v1
CACHET_TOKEN=your_api_token
METRIC_ID=1
TARGET_URL=https://your_domain

Crea el script:

sudo nano /usr/local/bin/cachet-metric.sh
#!/usr/bin/env bash
set -euo pipefail

# shellcheck source=/dev/null
source /etc/cachet-metrics.env

# Tiempo total de la petición en segundos, convertido a milisegundos enteros
seconds=$(curl -fsS -o /dev/null -w '%{time_total}' --max-time 10 "$TARGET_URL")
ms=$(awk -v s="$seconds" 'BEGIN { printf "%d", s * 1000 }')

curl -fsS -o /dev/null -X POST "$CACHET_URL/metrics/$METRIC_ID/points" \
  -H "X-Cachet-Token: $CACHET_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"value\": $ms, \"timestamp\": $(date +%s)}"

Hazlo ejecutable y pruébalo a mano:

sudo chmod 755 /usr/local/bin/cachet-metric.sh
sudo /usr/local/bin/cachet-metric.sh && echo OK
OK

Si la web no responde, curl -f devuelve un error, el script termina sin enviar un valor falso y el punto simplemente no se registra. Programa la ejecución cada minuto con cron:

echo '* * * * * root /usr/local/bin/cachet-metric.sh' | sudo tee /etc/cron.d/cachet-metric

Tras unos minutos, la gráfica aparecerá en la página pública bajo los componentes.

Paso 10: Hacer copias de seguridad

Todos los datos de Cachet están en PostgreSQL. Haz un volcado comprimido con pg_dump desde el contenedor:

mkdir -p ~/backups
docker compose -f /opt/cachet/compose.yaml exec -T postgres pg_dump -U cachet cachet | gzip > ~/backups/cachet-$(date +%F).sql.gz
ls -lh ~/backups

Guarda también /opt/cachet/.env, que contiene APP_KEY. Sin esa clave, una restauración de la base de datos no será utilizable.

Solución de problemas

El navegador muestra un error 500. Revisa los logs de la aplicación con docker compose logs --tail 100 cachet. La causa más común es una APP_KEY vacía o con formato incorrecto: debe empezar por base64:.

Cachet no conecta con la base de datos. Comprueba que PostgreSQL está healthy con docker compose ps y que DB_PASSWORD no ha cambiado desde el primer arranque. PostgreSQL solo aplica POSTGRES_PASSWORD al crear el volumen; si cambias la contraseña después, debes cambiarla también dentro de la base de datos.

Los suscriptores no reciben correos. Verifica las variables MAIL_* y que tu servidor puede salir al puerto SMTP con nc -vz smtp.your_domain 587. Algunos proveedores bloquean el puerto 25 saliente, así que usa el 587 con TLS.

La API devuelve 401 Unauthorized. El token es incorrecto o falta la cabecera X-Cachet-Token. Cópialo de nuevo desde tu perfil en el panel.

Conclusión

Tienes Cachet funcionando en Ubuntu 24.04 con Docker Compose y PostgreSQL, publicado en tu dominio con HTTPS, con componentes, incidentes y una métrica que se actualiza cada minuto. Como siguientes pasos, puedes integrar la API con tu sistema de alertas (Uptime Kuma, Prometheus Alertmanager o Zabbix) para abrir incidentes automáticamente, programar mantenimientos desde el panel y automatizar las copias de seguridad con un temporizador de systemd.