cipi deploy

Cipi usos Implementador para todas las implementaciones. Cada implementación es atómica: una nueva versión El directorio está completamente preparado antes de la current El enlace simbólico se intercambia, por lo que el tráfico nunca se interrumpe. interrumpido.

desde v4.5.4 Cipi paquetes Desplegador 8, que requiere PHP ≥ 8,3. cipi deploy y cipi deploy --rollback cancelar con un mensaje de actualización claro antes invocar Deployer cuando una aplicación todavía está fijada a una versión anterior PHP; cámbiela con cipi app edit <app> --php=8.3 (o superior) primero.

Implementar canalización

Deployer y Composer se ejecutan con el versión PHP configurada de la aplicación (por ej. /usr/bin/php8.5), no el valor predeterminado del sistema. Esto se aplica a cipi deploy, cipi deploy --rollback, activadores de implementación de crontab, cipi sync import despliega, y el deploy / composeralias en el usuario de la aplicación .bashrc.

  1. Detener trabajadores en cola (cipi worker stop)
  2. Clonar repositorio en releases/N/
  3. correr composer install --no-dev (con PHP de la aplicación)
  4. Enlace shared/.env y shared/storage/
  5. correr artisan migrate --force
  6. correr artisan optimize
  7. correr artisan storage:link
  8. Intercambiar current enlace simbólico atómicamente
  9. Reiniciar trabajadores de la cola
  10. Pode las versiones antiguas (conserve las últimas 5)
fiesta
$ cipi deploy myapp              # deploy latest commit
$ cipi deploy myapp --rollback   # instant rollback to previous release
$ cipi deploy myapp --releases   # list all releases with timestamps
$ cipi deploy myapp --key        # show the SSH deploy key
$ cipi deploy myapp --webhook    # show webhook URL and token
$ cipi deploy myapp --unlock     # remove a stuck deploy lock
$ cipi deploy myapp --snapshot   # v5.0+ opt-in DB dump before deploy
$ cipi deploy myapp --snapshot-required  # fail if snapshot cannot be taken
$ cipi deploy myapp --trust-host=git.mycompany.com       # trust a custom Git server fingerprint
$ cipi deploy myapp --trust-host=git.mycompany.com:2222  # trust on non-standard port (also writes ~/.ssh/config)

desde v5.0, inscribirse instantáneas de base de datos previas a la implementación están disponibles a través de --snapshot / --snapshot-required o una configuración de aplicación permanente; consulte Instantáneas de base de datos previas a la implementación. Octane aplicaciones usan el laravel-octane.php Plantilla de implementación (recargar/reiniciar Octane en la implementación); habilitar nodo construye con cipi app edit <app> --node-build='…'.

Si se interrumpe una implementación (por ejemplo, por un error de red), el implementador puede dejar un archivo de bloqueo. uso cipi deploy myapp --unlock para eliminarlo antes de volver a implementarlo.

Instantáneas de base de datos previas a la implementación

desde v5.0, cipi deploy <app> puede volcar la base de datos antes se ejecuta el proceso de lanzamiento. Las instantáneas aterrizan debajo /var/log/cipi/backups/ - el mismo camino utilizado por cipi db backup.

fiesta
$ cipi deploy shop --snapshot            # dump first; warn and continue on failure
$ cipi deploy shop --snapshot-required   # dump first; abort deploy if snapshot fails

que pasa

  1. Antes de que se inicie Deployer, Cipi realiza un volcado de base de datos en /var/log/cipi/backups/.
  2. con --snapshot-required, un volcado fallido (o falta un motor de base de datos) bloques el despliegue.
  3. con --snapshot solo, un volcado fallido imprime una advertencia y la implementación continúa.
--snapshotVolcado de participación antes de la implementación; avisar y continuar si no se puede tomar la instantánea.
--snapshot-requiredEl mismo volcado, pero falla la implementación cuando la instantánea (o el motor) no está disponible.

Habilitar en cada implementación

Actívelo permanentemente para una aplicación de modo que cada implementación (CLI, webhook o canalización) tome primero una instantánea:

fiesta
$ cipi app edit shop --predeploy-snapshot
cipi deploy --rollback restaura el anterior código liberación solamente. lo hace no restaurar la base de datos. Si necesita recuperar el volcado previo a la implementación, utilice cipi db restore.

Para flujos de trabajo de canalización que también archivan shared/ a S3 antes del lanzamiento, consulte Implementación segura: copia de seguridad antes del lanzamiento.

cipi app deploy-config

desde v5.0.3, administrar opciones duraderas de recetas de Deployer almacenadas en apps.json y se aplica regenerando deploy.php de la plantilla - una alternativa segura a la edición de formato libre PHP.

fiesta
$ cipi app deploy-config myapp
$ cipi app deploy-config myapp --keep-releases=5
$ cipi app deploy-config myapp --migrate --optimize --storage-link
$ cipi app deploy-config myapp --no-migrate --no-optimize
$ cipi app deploy-config myapp --queue-restart --horizon-terminate
$ cipi app deploy-config myapp --extra-artisan=view:clear,event:cache
$ cipi app deploy-config myapp --node-build='npm ci && npm run build'
$ cipi app deploy-config myapp --predeploy-snapshot

DESCANSO: GET|PUT /api/apps/{name}/deploy-config (capacidad apps-deploy-config, API 1.14+ / Cipi 5.0.3+). MCP: AppDeployConfigShow, AppDeployConfigUpdate.

auth.json

Gestionar el auth.json archivo para una aplicación. Este archivo vive en /home/<app>/shared/auth.json y tiene un enlace simbólico automático en cada versión mediante Implementador: exactamente igual .env. Úselo para almacenar datos de credenciales estructurados (por ejemplo, API claves, indicadores de funciones o cualquier carga útil JSON) que su aplicación Laravel pueda leer en tiempo de ejecución.

fiesta
$ cipi auth create myapp   # create auth.json with initial { "users": [] } structure
$ cipi auth edit myapp     # open in $EDITOR (fallback: nano), validate JSON on close
$ cipi auth show myapp     # print contents formatted with jq
$ cipi auth delete myapp   # delete file (asks for confirmation)

# Non-interactive (v5.0.3+) — API / scripts / GUI
$ cipi auth create myapp --force
$ cipi auth edit myapp --file=/tmp/auth.json
$ cipi auth show myapp --json
$ cipi auth delete myapp --force

DESCANSO: GET|POST|PUT|DELETE /api/apps/{name}/auth (capacidad apps-auth, API 1.14+) — Composer/estructurado JSON, distinto de HTTP Autenticación básica. MCP: AppAuthJsonShow, AppAuthJsonCreate, AppAuthJsonUpdate, AppAuthJsonDelete.

Detalles del comando

Comando Descripción
cipi auth create <app> Crea shared/auth.json con la estructura inicial {"users":[]}, establece permisos para 640 (propietario app:app), y añade auth.json a shared_files en la configuración del implementador de la aplicación para que tenga un enlace simbólico en cada desplegar.
cipi auth edit <app> Abre shared/auth.json en $EDITOR (vuelve a nano). Después de que se cierre el editor, valide JSON con jq y advierte si el archivo tiene un formato incorrecto.
cipi auth show <app> Imprime el contenido de shared/auth.json formateado con jq.
cipi auth delete <app> Pide confirmación y luego lo elimina. shared/auth.json y elimina el auth.json entrada desde shared_files en el implementador de la aplicación configuración.

Integración del implementador

cipi auth create agrega automáticamente auth.json al shared_files lista en /home/<app>/.deployer/deploy.php, y cipi auth delete lo elimina. Esto significa que el archivo se trata exactamente como .env: persiste en todas las versiones y nunca se sobrescribe con una implementación.

cada cipi auth La operación se registra mediante log_action para auditabilidad. el AUTH La sección también aparece en la salida de cipi help.

Proveedores de Git

Cipi está listo para trabajar con GitHub y GitLab pero soporta cualquier otro proveedor de Git que admita claves de implementación SSH, sin dependencia del proveedor.

Para self-hosted o servidores Git personalizados, debe confiar en la huella digital del host del servidor antes El implementador puede clonar a través de SSH. Utilice el --trust-host bandera para agregar la huella digital a la usuario de la aplicación ~/.ssh/known_hosts automáticamente:

fiesta
# show the deploy key and add it to your Git provider
$ cipi deploy myapp --key

# trust a custom Git server fingerprint (standard port)
$ cipi deploy myapp --trust-host=git.mycompany.com

# trust a custom Git server on a non-standard port
# (also writes ~/.ssh/config automatically)
$ cipi deploy myapp --trust-host=git.mycompany.com:2222
Cuando se especifica un puerto no estándar, Cipi también escribe el Host / Port entrada al usuario de la aplicación ~/.ssh/config entonces ese Deployer puede llegar al servidor sin ninguna configuración adicional.

configuración automática de git

Si guardas un GitHub o GitLab Token de acceso personal, Cipi agrega automáticamente la clave de implementación SSH y crea webhook en el repositorio cada vez que ejecutacipi app create. No se requieren pasos manuales.

guardar una ficha

fiesta
# GitHub (fine-grained or classic PAT)
$ cipi git github-token ghp_xxxxxxxxxxxxxxxxxxxx

# GitLab (gitlab.com)
$ cipi git gitlab-token glpat-xxxxxxxxxxxxxxxxxxxx

# GitLab (self-hosted — set the URL before or after the token)
$ cipi git gitlab-url https://gitlab.example.com
$ cipi git gitlab-token glpat-xxxxxxxxxxxxxxxxxxxx

GitHub permisos de token

Se necesitan tokens detallados (recomendados) administración y Ganchos web establecer en leer y escribir en los repositorios de destino. Fichas clásicas necesito el repo alcance.

GitLab permisos de token

el api el alcance es el mínimo requerido: GitLab no ofrece un alcance más granular que cubre tanto las claves de implementación como los webhooks.

Ciclo de vida automático

Evento Qué hace Cipi automáticamente
app create Agrega la clave de implementación + crea webhook en el repositorio a través de API. El resumen muestra "configurado automáticamente ✓" en lugar de instrucciones manuales.
app edit --repository=... Elimina la clave de implementación + webhook del repositorio anterior y luego las agrega al nuevo.
app delete Elimina la clave de implementación + webhook del repositorio antes de eliminar la aplicación.

cipi git comandos

Comando Descripción
cipi git status Mostrar el estado de conexión del proveedor y los detalles de integración por aplicación (implementar ID de clave, webhook ID)
cipi git github-token <token> Guarde un GitHub token de acceso personal
cipi git gitlab-token <token> Guarde un GitLab token de acceso personal
cipi git gitlab-url <url> Establecer la URL base para una instancia self-hosted GitLab
cipi git remove-github Eliminar el token GitHub almacenado
cipi git remove-gitlab Eliminar el token GitLab y la URL almacenados

Configuración manual (alternativa)

La configuración automática se omite cuando no se configura ningún token, cuando falla la llamada API (permisos incorrectos, repositorio no encontrado, límite de velocidad), o cuando el repositorio está alojado en un proveedor que no sea GitHub o GitLab (por ejemplo, Gitea, Forgejo, Bitbucket). En todos estos casos Cipi vuelve al flujo de trabajo manual y la creación de la aplicación continúa normalmente.

Para configurar la clave de implementación y webhook manualmente:

fiesta
# print the SSH deploy key to add to your Git provider
$ cipi deploy myapp --key

# print the webhook URL and token
$ cipi deploy myapp --webhook

# if using a custom Git server, trust the host fingerprint first
$ cipi deploy myapp --trust-host=git.mycompany.com

Luego agréguelos en la configuración del repositorio de su proveedor:

  • Implementar clave — GitHub: Configuración → Implementar claves → Agregar clave de implementación; GitLab: Configuración → Repositorio → Implementar claves
  • Webhook — GitHub: Configuración → Webhooks → Agregar webhook; GitLab: Configuración → Webhooks → Agregar nuevo webhook. Establezca la URL de carga útil y secreto de los valores mostrados por cipi deploy myapp --webhook
Si elimina un token de proveedor después de que se hayan creado aplicaciones con la configuración automática, Cipi no podrá limpiar las claves de implementación y los webhooks cuando elimine o edite esas aplicaciones. una advertencia es se muestra y deberá eliminarlos manualmente desde la configuración del repositorio del proveedor.

Personalización del script de implementación

La configuración de implementación para cada aplicación se almacena. en:

/home/miaplicación/.deployer/deploy.php

Este archivo es generado automáticamente por Cipi durante app create y se actualiza automáticamente cuando cambie la versión PHP o implemente la rama a través de cipi app edit. Puedes editarlo para personalizarlo. la canalización de implementación, pero debe comprender las implicaciones antes de hacerlo.

Canalización de implementación predeterminada

El autogenerado deploy.php ejecuta estas tareas en orden:

php
deploy:prepare          // create releases/N/ directory
deploy:vendors          // composer install --no-dev
deploy:shared           // link shared/.env and shared/storage/
artisan:migrate         // php artisan migrate --force
artisan:optimize        // php artisan optimize
artisan:storage:link    // php artisan storage:link
deploy:symlink          // swap current → releases/N/ atomically
cipi:restart-workers    // supervisorctl restart myapp-*
deploy:cleanup          // keep last 5 releases, delete older

Agregar tareas personalizadas

Puede agregar tareas antes o después de cualquier paso. Para ver un ejemplo completo de compilación de frontend (npm install && npm run build), ver Construyendo activos frontend abajo. Otros ejemplos comunes:

php
// Run artisan db:seed after migrations
after('artisan:migrate', 'artisan:db:seed');

// Clear view cache after symlink swap
after('deploy:symlink', 'artisan:view:clear');

// Custom task — send a Slack notification
task('notify:slack', function () {
    run('curl -X POST https://hooks.slack.com/... -d \'{"text":"Deployed!"}\'');
});
after('deploy:symlink', 'notify:slack');

Creación de activos de interfaz (npm / Vite)

no hay dedicado cipi Marca CLI para compilaciones de frontend (p. ej. npm install && npm run build). Personalización deploy.php es el enfoque apoyado y esperado - definir un Implementador task() y engancharlo con after() o before(). tu no es necesario evitar esto; extender la canalización es exactamente para lo que sirve el archivo.

Cipi instala Node.js y npm en el servidor durante la instalación. Verifique que estén disponibles como el usuario de la aplicación:

fiesta
$ ssh myapp@your-server-ip
myapp@server:~$ node -v && npm -v

comprometerse package.json y package-lock.json a su repositorio. Engancha la construcción después deploy:shared entonces .env está vinculado (Vite lee VITE_* variables a partir de ahí) y antes deploy:symlink entonces Los activos compilados existen en la versión antes de que entre en funcionamiento.

Agregue el bloque a continuación en el abajo de /home/myapp/.deployer/deploy.php, debajo de las definiciones de tareas generadas automáticamente por Cipi:

php
// ── Custom: frontend build (safe zone — keep below Cipi-managed blocks) ──

task('npm:build', function () {
    cd('{{release_path}}');
    run('npm ci --no-audit --no-fund && npm run build');
});

// .env is linked → build assets → then migrations / optimize / symlink
after('deploy:shared', 'npm:build');

Esto equivale a npm install && npm run build en cada despliegue. Prefiero npm ci en producción cuando package-lock.json está comprometido: es más rápido y reproducible. uso npm install en cambio, solo si no bloquea las dependencias.

Si necesita pasos de instalación y compilación separados (por ejemplo, almacenar en caché node_modules en todas las versiones), dividirlos en dos tareas:

php
task('npm:ci', function () {
    cd('{{release_path}}');
    run('npm ci --no-audit --no-fund');
});

task('npm:build', function () {
    cd('{{release_path}}');
    run('npm run build');
});

after('deploy:shared', 'npm:ci');
after('npm:ci', 'npm:build');

Para acelerar implementaciones posteriores, puede conservar las dependencias entre versiones agregando node_modules a los directorios compartidos del implementador (opcional, solo si su proyecto admite eso):

php
add('shared_dirs', ['node_modules']);

Edite el archivo en el servidor como usuario de la aplicación, luego pruebe con cipi deploy myapp:

fiesta
$ ssh myapp@your-server-ip
myapp@server:~$ nano ~/.deployer/deploy.php
# paste the custom tasks at the bottom, save, then as root:
$ cipi deploy myapp
Alternativa: correr npm ci && npm run build en GitHub Acciones o GitLab CI antes el paso de implementación SSH, por lo que el servidor solo recibe activos prediseñados. Ver CI/CD canalizaciones: implementación de SSH.

Ejecutando comandos artisan adicionales

php
// Seed only in specific environments
task('artisan:db:seed', function () {
    run('{{bin/php}} {{release_path}}/artisan db:seed --force');
});
Cipi puede sobrescribir la implementación.php cuando corres cipi app edit myapp --php=X o cipi app edit myapp --branch=X. hacer copia de seguridad tus personalizaciones o mantenlas en una sección claramente separada de los bloques administrados por Cipi. un El patrón seguro es colocar todas las tareas personalizadas al final del archivo después de la tarea predeterminada. definición.

Deshabilitar un paso predeterminado

Para omitir una tarea (por ejemplo, si maneja las migraciones manualmente), coméntela o elimínela del deploy definición de tarea:

php
// Remove the migrate step from the pipeline
task('deploy', [
    'deploy:prepare',
    'deploy:vendors',
    'deploy:shared',
    // 'artisan:migrate',  ← disabled
    'artisan:optimize',
    'artisan:storage:link',
    'deploy:symlink',
    'cipi:restart-workers',
    'deploy:cleanup',
]);

Probando tus cambios

Después de editar deploy.php, siempre haga una implementación de prueba antes de pasar a producción:

fiesta
$ cipi deploy myapp

# If something goes wrong, instant rollback:
$ cipi deploy myapp --rollback

# If the deploy is stuck (e.g. interrupted mid-run):
$ cipi deploy myapp --unlock
El registro de implementación siempre está disponible en ~/logs/deploy.log o vía cipi app logs myapp --type=deploy. Compruébelo primero cuando solucione un problema desplegar.

Implementar y CI/CD: descripción general

Con Cipi, CI (construir y probar) y CD (lanzamiento a producción) puede ser divididos o combinados. En última instancia, cada implementación se ejecuta de la misma manera. Implementador tubería en el servidor - clonar, composer install, migraciones, intercambio de enlaces simbólicos, reinicio del trabajador. lo que cambia es que desencadena ese oleoducto.

Cipi admite dos modelos de disparador. Para la mayoría de las aplicaciones Laravel, comience con webhook + Cipi Agente camino. Pase a una canalización CI/CD completa cuando necesite puertas, copias de seguridad o orquestación de infraestructura que un simple gancho de empuje no puede expresar.

Dos formas de activar una implementación

Webhook + Cipi Agente (recomendado) CI/CD canalización a través de SSH
gatillo PUBLICACIONES del proveedor de Git para /cipi/webhook al empujar GitHub Acciones / GitLab Ejecuciones de trabajos de CI cipi deploy a través de SSH
Acceso al servidor desde CI Ninguno: solo HTTPS para el dominio de tu aplicación Clave SSH dedicada almacenada como secreto de CI
Pruebas previas a la implementación Ejecutar localmente o en un trabajo de CI independiente; El despliegue aún se activa al presionar a menos que desactives el webhook Nativo: el paso de implementación se ejecuta solo después needs: test (o equivalente) pases
Copia de seguridad antes del lanzamiento Manual o cron en el servidor Trabajo de canalización — ver despliegue seguro
Vista previa/revisión de aplicaciones No compatible desde el primer momento Pipeline crea Cipi aplicaciones por rama; consulte vista previa entornos
Complejidad de configuración Bajo — composer require cipi/agent + uno webhook Medio: clave SSH, secretos, flujo de trabajo YAML

¿Qué enfoque debo utilizar?

Caso de uso Enfoque recomendado Dónde leer más
Aplicación única Laravel, push-to-implementar activada main Webhook + Agente Webhook configuración
Implementar solo si se pasan las pruebas de CI Canalización SSH (deshabilitar producción webhook) Implementación de canalización SSH
Copia de seguridad de archivos DB + antes de cada lanzamiento de producción Tubería SSH Implementación segura con respaldo
Alertas de Slack/Telegram sobre el resultado de la implementación Tubería SSH Implementar notificaciones
URL efímera por rama de función (revisar aplicaciones) Tubería SSH Vista previa de entornos
Implemente varias aplicaciones en un servidor desde un repositorio Ya sea: webhook por aplicación o una canalización con paralelo cipi deploy Implementación de múltiples aplicaciones
Elija un activador por aplicación. No deje una producción webhook activa mientras también ejecutar implementaciones de canalización al insertar: dos ejecuciones simultáneas del implementador entran en conflicto en el archivo de bloqueo. uso cipi deploy myapp --unlock si se deja una cerradura atascada.

Implementaciones automáticas: Cipi Agente y webhook

cipi-agente (cipi/agent) es un paquete Laravel que expone POST /cipi/webhook dentro de su aplicación en ejecución. Cuando GitHub o GitLab envía un push evento, el agente valida la firma de la carga útil, reconoce inmediatamente y pone en cola una implementación en el servidor: no hay SSH del corredor CI, no sudo, no hay puertos de entrada abiertos más allá de HTTPS.

Cómo funciona el flujo webhook

El diseño separa acuse de recibo rápido HTTP de Implementador lento trabajo. Una implementación puede tardar varios minutos; Los proveedores de Git agotan el tiempo de espera de webhook HTTP llamadas después de ~10 segundos. Cipi resuelve esto con un archivo de bandera y el crontab del usuario de la aplicación.

fluir
  Developer                    Git provider              Your Laravel app (Cipi Agent)           Server (app user cron)
      │                              │                              │                                        │
      │  git push main               │                              │                                        │
      │ ───────────────────────────► │                              │                                        │
      │                              │  POST /cipi/webhook          │                                        │
      │                              │  (signed with secret)        │                                        │
      │                              │ ───────────────────────────► │                                        │
      │                              │                              │ 1. Verify CIPI_WEBHOOK_TOKEN           │
      │                              │                              │ 2. Check branch (CIPI_DEPLOY_BRANCH)   │
      │                              │                              │ 3. Write ~/.deploy-trigger             │
      │                              │ ◄─────────────────────────── │ 4. Return 200 immediately              │
      │                              │                              │                                        │
      │                              │                              │         every minute (* * * * *)       │
      │                              │                              │ ◄──────────────────────────────────────│
      │                              │                              │         cron sees .deploy-trigger      │
      │                              │                              │         removes file, runs Deployer    │
      │                              │                              │         in background as app user      │
      │                              │                              │                                        │
      │                              │                              │         clone → composer → migrate     │
      │                              │                              │         → symlink swap → workers       │

El implementador siempre se ejecuta como aplicación usuario de Linux (por ej. myapp), con el corregir PHP permisos binarios y de archivos: el mismo contexto que un manual cipi deploy myapp. El webhook nunca paga directamente al Deployer; solo deja caer el archivo desencadenante que el crontab de Cipi ya supervisa.

Requisitos previos

Requisito ¿Por qué?
Aplicación Cipi creada con el repositorio Git La clave de implementación debe clonar el repositorio; consulte configuración automática de git
Al menos una implementación manual exitosa El paquete del agente debe estar presente en el current liberar antes del webhook la ruta existe
composer require cipi/agent en el proyecto Registra el /cipi/webhook validación de ruta y firma
Webhook URL accesible a través de HTTPS Los proveedores de Git requieren una URL pública; usar cipi ssl install primero
CIPI_WEBHOOK_TOKEN en shared/.env Generado automáticamente en cipi app create; compartido en todas las versiones

Configuración paso a paso

1. Cree la aplicación e impleméntela una vez manualmente. para que el servidor pueda clonar su repositorio:

fiesta
$ cipi app create --user=myapp --domain=myapp.com \
    --repository=git@github.com:you/myapp.git --branch=main --php=8.5
$ cipi deploy myapp

2. Instale el agente Cipi en su proyecto Laravel localmente, confirme y envíe:

fiesta
$ composer require cipi/agent
$ git add composer.json composer.lock
$ git commit -m "Add Cipi Agent for webhook deploys"
$ git push origin main
$ cipi deploy myapp   # one more manual deploy until webhook is live

3. Configure el webhook. Si guardó un token GitHub o GitLab, es posible que Cipi tenga ya creó el webhook durante app create - consultar con cipi git status. De lo contrario, recupere la URL y el secreto:

fiesta
$ cipi deploy myapp --webhook

Agregue webhook en su proveedor de Git:

Proveedor URL de carga útil campo secreto Eventos
GitHub https://myapp.com/cipi/webhook Secreto → valor de --webhook Sólo el push evento
GitLab https://myapp.com/cipi/webhook Token secreto → mismo valor Eventos push

4. Restringir a su sucursal de implementación (recomendado para producción):

ambiente
CIPI_DEPLOY_BRANCH=main

Establecer esto en shared/.env vía cipi app env myapp. Empujes a otras ramas recibir un skipped respuesta y no se realizan ejecuciones de implementación.

5. Verificar. Empuje un pequeño compromiso para main y mire el registro de implementación:

fiesta
$ cipi app logs myapp --type=deploy
# or on the server as the app user:
$ tail -f /home/myapp/logs/deploy.log

Aproximadamente un minuto después de la entrega webhook, debería aparecer una nueva versión de Deployer. Confirma el compromiso en vivo con php artisan cipi:status o el salud comprobar punto final.

configuración automática de git

cuando un GitHub o GitLab token está configurado en el servidor, Cipi registra la clave de implementación y crea el webhook automáticamente en cada cipi app create. El resumen de la aplicación muestra "configurado automáticamente ✓" en lugar de manual instrucciones. Eventos del ciclo de vida (app edit --repository, app delete) mantener claves y webhooks sincronizados.

Solución de problemas

Síntoma causa probable Arreglar
Webhook devuelve 404 Agente aún no implementado correr cipi deploy myapp después de agregar cipi/agent a composer.json
Webhook devuelve 403/firma no válida discrepancia secreta Vuelva a copiar el token de cipi deploy myapp --webhook en la configuración del proveedor
200 OK pero sin implementación Rama filtrada comprobar CIPI_DEPLOY_BRANCH coincide con la rama empujada
Implementación atascada/error de bloqueo Despliegue anterior interrumpido cipi deploy myapp --unlock luego vuelve a intentarlo
Implementar ejecuciones dos veces con una sola pulsación Webhook + canalización ambas activas Desactive un activador: consulte descripción general
El agente también admite implementaciones manuales y activadas por IA a través de MCP deploy herramienta— usa lo mismo .deploy-trigger mecanismo. Ver Cipi Agente para controles de estado, MCP y funciones de anonimización.

CI/CD canalizaciones: implementación de SSH

Cuando el modelo webhook no sea suficiente, ejecute GitHub Acciones o GitLab CI/CD trabajos que SSH ingresan al servidor e invocan cipi deploy. Este es el elección correcta siempre que se deba realizar la implementación condicional - controlado en pruebas, precedido por copias de seguridad, seguido de notificaciones u orquestando nuevas aplicaciones de vista previa.

Cuando necesita una canalización en lugar de webhook

  • Puerta de calidad - correr php artisan test, análisis estático o frontend se construye antes de que cualquier código llegue a producción
  • Liberación segura - tomar una instantánea de la base de datos y shared/ a S3 antes intercambiando el enlace simbólico (despliegue seguro)
  • Visibilidad del equipo — publicar éxito/fallo en Slack o Telegram con la reversión activada fracaso (implementar notificaciones)
  • Revisar aplicaciones — crear o actualizar una aplicación Cipi completa por rama de funciones (entornos de vista previa)
  • Monorepo multiaplicación - implementar frontend y api en paralelo después de un único trabajo de prueba

Para estos flujos de trabajo, desactivar la producción webhook (o nunca crear uno) así que solo se implementan los desencadenadores de la canalización. Aún puedes usar Cipi Agent en la aplicación para controles de estado y MCP.

Acceso SSH para CI

Generar un dedicado ed25519 par de claves para el corredor CI. Añade el clave pública a /root/.ssh/authorized_keys en el servidor (o el cipi usuario si lo prefieres sudo cipi deploy) y almacenar el clave privada como un secreto de CI. Nunca reutilice las claves de implementación de Git o las claves SSH personales.
fiesta
# on your local machine
$ ssh-keygen -t ed25519 -C "ci-deploy" -f ~/.ssh/ci_deploy -N ""

# copy the public key to the server
$ ssh-copy-id -i ~/.ssh/ci_deploy.pub root@your-server-ip

# copy the private key content → add it as a CI secret (SERVER_SSH_KEY)
$ cat ~/.ssh/ci_deploy

Tienda SERVER_HOST (IP del servidor o nombre de host) junto a SERVER_SSH_KEY en tu secretos del repositorio (GitHub) o CI/CD variables (GitLab).

GitHub Acciones: prueba y luego implementa

Agregue la clave privada como un secreto de repositorio llamado SERVER_SSH_KEY y la IP del servidor como SERVER_HOST.

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: php artisan test

  deploy:
    runs-on: ubuntu-latest
    needs: test          # only deploy if tests pass
    steps:
      - name: Deploy via Cipi
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: cipi
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: sudo cipi deploy myapp

Para revertir una falla, extienda el paso del guión:

yaml
          script: |
            sudo cipi deploy myapp || (sudo cipi deploy myapp --rollback && exit 1)

GitLab CI/CD

Agregue la clave privada como una variable CI/CD denominada SERVER_SSH_KEY (tipo: Archivo) y el servidor propiedad intelectual como SERVER_HOST.

yaml
# .gitlab-ci.yml
stages:
  - test
  - deploy

test:
  stage: test
  script:
    - php artisan test

deploy:
  stage: deploy
  environment: production
  only:
    - main
  before_script:
    - apt-get install -y openssh-client
    - eval $(ssh-agent -s)
    - echo "$SERVER_SSH_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - ssh-keyscan -H $SERVER_HOST >> ~/.ssh/known_hosts
  script:
    - ssh root@$SERVER_HOST "cipi deploy myapp"

Con reversión en caso de falla:

yaml
  script:
    - ssh root@$SERVER_HOST "cipi deploy myapp || (cipi deploy myapp --rollback && exit 1)"

Implementación de múltiples aplicaciones

Si la misma canalización administra varias aplicaciones en el mismo servidor:

yaml
# GitHub Actions — deploy multiple apps in parallel
      - name: Deploy
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cipi deploy frontend &
            cipi deploy api &
            wait

Patrones de canalización avanzados

Una vez que la implementación de SSH funcione, componga estas secciones en un único flujo de trabajo de producción:

Patrón Lo que añade el oleoducto Guía
Notificaciones Mensaje de Slack o Telegram sobre éxito, fracaso y reversión automática Implementar notificaciones
Implementación segura cipi db backup + cipi backup run antes cipi deploy; revertir en caso de falla Implementación segura con respaldo
Vista previa de entornos Crear/actualizar/eliminar aplicaciones por rama Cipi con comodín DNS + SSL Vista previa de entornos

Una configuración madura típica utiliza el webhook para una aplicación de preparación (comentarios instantáneos sobre cada empujar) y un tubería para producción (pruebas → copia de seguridad → implementar → notificar). Cada aplicación tiene su propio desencadenante: nunca entran en conflicto porque se dirigen a diferentes Cipi usuarios de la aplicación.

Implementar notificaciones

Caso de uso de canalización: la ruta webhook se implementa silenciosamente: Git devuelve 200 y el equipo se entera solo si miran los registros. con un canalización SSH, agregar pasos de notificación despuéscipi deploy para transmitir éxito, fracaso y automático. reversiones a Slack o Telegram. Los dos ejemplos siguientes funcionan con GitHub acciones y GitLab CI usando solo llamadas HTTP estándar, sin dependencias de plataforma adicionales.

flojo

Agregue un paso final que se publique en Slack webhook independientemente del resultado de la implementación. uso if: always() en GitHub Acciones para que la notificación se active tanto en caso de éxito como de fracaso.

Crear un entrante Webhook en su espacio de trabajo de Slack y almacene la URL como SLACK_WEBHOOK_URL en tus secretos de CI.

yaml
# GitHub Actions — deploy + Slack notification
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        id: deploy
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: cipi deploy myapp

      - name: Notify Slack — success
        if: success()
        uses: slackapi/slack-github-action@v2
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: incoming-webhook
          payload: |
            {
              "text": ":white_check_mark: *myapp* deployed successfully",
              "attachments": [{
                "color": "good",
                "fields": [
                  { "title": "Branch",  "value": "${{ github.ref_name }}", "short": true },
                  { "title": "By",      "value": "${{ github.actor }}",    "short": true },
                  { "title": "Commit",  "value": "${{ github.sha }}",      "short": false }
                ]
              }]
            }

      - name: Notify Slack — failure
        if: failure()
        uses: slackapi/slack-github-action@v2
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: incoming-webhook
          payload: |
            {
              "text": ":x: *myapp* deploy FAILED — rolling back",
              "attachments": [{
                "color": "danger",
                "fields": [
                  { "title": "Branch", "value": "${{ github.ref_name }}", "short": true },
                  { "title": "By",     "value": "${{ github.actor }}",    "short": true },
                  { "title": "Run",    "value": "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", "short": false }
                ]
              }]
            }

      - name: Rollback on failure
        if: failure()
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: cipi deploy myapp --rollback

Para GitLab CI, utilice curl directamente, no se necesita complemento:

yaml
# .gitlab-ci.yml — deploy stage with Slack notification
deploy:
  stage: deploy
  script:
    - ssh root@$SERVER_HOST "cipi deploy myapp" && export DEPLOY_STATUS="success" || export DEPLOY_STATUS="failed"
    - |
      if [ "$DEPLOY_STATUS" = "success" ]; then
        curl -s -X POST "$SLACK_WEBHOOK_URL" \
          -H "Content-Type: application/json" \
          -d "{\"text\":\":white_check_mark: *myapp* deployed by $GITLAB_USER_LOGIN on \`$CI_COMMIT_REF_NAME\`\"}"
      else
        curl -s -X POST "$SLACK_WEBHOOK_URL" \
          -H "Content-Type: application/json" \
          -d "{\"text\":\":x: *myapp* deploy FAILED — <$CI_PIPELINE_URL|view pipeline>\"}"
        ssh root@$SERVER_HOST "cipi deploy myapp --rollback"
        exit 1
      fi

Telegrama

Crea un bot de Telegram a través de @BotFather, obtenga el token del bot y busque su ID de chat/grupo. Guárdalos como TELEGRAM_BOT_TOKEN y TELEGRAM_CHAT_ID en secretos de CI.

yaml
# GitHub Actions — deploy + Telegram notification
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy
        id: deploy
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: cipi deploy myapp

      - name: Notify Telegram — success
        if: success()
        run: |
          curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
            -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
            -d parse_mode="Markdown" \
            -d text="✅ *myapp* deployed successfully%0ABranch: \`${{ github.ref_name }}\`%0ABy: ${{ github.actor }}"

      - name: Notify Telegram — failure + rollback
        if: failure()
        run: |
          curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
            -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
            -d parse_mode="Markdown" \
            -d text="❌ *myapp* deploy FAILED — rolling back%0ABranch: \`${{ github.ref_name }}\`%0A[View run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
          ssh -o StrictHostKeyChecking=no -i <(echo "${{ secrets.SERVER_SSH_KEY }}") \
            root@${{ secrets.SERVER_HOST }} "cipi deploy myapp --rollback"

GitLab CI equivalente (puro curl, sin dependencias adicionales):

yaml
# .gitlab-ci.yml — deploy stage with Telegram notification
deploy:
  stage: deploy
  script:
    - ssh root@$SERVER_HOST "cipi deploy myapp" && RESULT="✅ deployed" || RESULT="❌ FAILED"
    - |
      curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
        -d chat_id="$TELEGRAM_CHAT_ID" \
        -d parse_mode="Markdown" \
        -d text="*myapp* ${RESULT}%0ABranch: \`$CI_COMMIT_REF_NAME\`%0ABy: $GITLAB_USER_LOGIN"
    - |
      if echo "$RESULT" | grep -q "FAILED"; then
        ssh root@$SERVER_HOST "cipi deploy myapp --rollback"
        exit 1
      fi
Para encontrar su ID de chat de Telegram, agregue el bot al grupo/canal objetivo, luego llame https://api.telegram.org/bot<TOKEN>/getUpdates y busca el chat.id campo en la respuesta. Para chats privados, simplemente envíe un mensaje al bot primero.

Implementación segura: copia de seguridad antes del lanzamiento

Para un volcado integrado justo antes de que se ejecute Deployer (no se requiere etapa de canalización), use --snapshot / --snapshot-requiredo habilitar cipi app edit <app> --predeploy-snapshot.

Caso de uso de canalización: una implementación webhook no puede ejecutar un paso de copia de seguridad antes de publicar el código: los disparos del evento push se despliegan inmediatamente. en un CI/CD canalización, agrega un dedicado backup etapa que debe triunfar antes deploy comienza. un El flujo de trabajo de nivel de producción siempre debe crear un punto de restauración. antes el nuevo código va vivir. Cipi proporciona dos comandos de respaldo complementarios que se asignan a dos niveles de seguridad diferentes:

fiesta
# local DB snapshot — fast, on-disk, instant rollback
$ cipi db backup myapp
# → /var/log/cipi/backups/myapp_20260303_143012.sql.gz

# S3 backup — DB dump + shared/ folder uploaded to your bucket
$ cipi backup run myapp
# → s3://your-bucket/cipi/myapp/2026-03-03_143015/db.sql.gz
# → s3://your-bucket/cipi/myapp/2026-03-03_143015/shared.tar.gz

Usados juntos en una canalización, le brindan un punto de restauración local rápido y una copia fuera del servidor de la base de datos y todos los archivos cargados. La implementación solo comienza si ambas copias de seguridad se realizan correctamente.

Requisito previo: correr cipi backup configure una vez en el servidor para vincule sus credenciales S3 antes cipi backup run se puede utilizar. cipi db backup Funciona sin ninguna configuración: siempre está disponible.

Qué hace cada comando internamente

cipi db backup <app> llamadas mysqldump --single-transaction --routines --triggers y gzips la salida a /var/log/cipi/backups/<app>_<timestamp>.sql.gz. El archivo permanece en el servidor y nunca se elimina automáticamente: agregue un paso de limpieza o un cron si el espacio en disco es importante.

cipi backup run <app> hace dos cosas: vuelca la base de datos con mariadb-dump --single-transaction en un directorio temporal y archiva todo el /home/<app>/shared/ carpeta (que contiene .env, storage/y cualquier archivo subido por el usuario). Luego, ambos archivos se cargan en S3 bajo el camino cipi/<app>/<timestamp>/. Los archivos temporales se eliminan después de una exitosa subir.

GitHub Acciones: flujo de trabajo de implementación segura

yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: php artisan test

  backup:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - name: Local DB backup
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: cipi db backup myapp

      - name: S3 backup (DB + shared)
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: cipi backup run myapp

  deploy:
    runs-on: ubuntu-latest
    needs: backup          # only runs if backup job succeeds
    steps:
      - name: Deploy
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: cipi deploy myapp

      - name: Rollback on failure
        if: failure()
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            cipi deploy myapp --rollback
            echo "Deploy failed — rolled back to previous release"

El gráfico de trabajos impone el orden: testbackupdeploy. si cualquier trabajo falla, los siguientes se omiten. Si el paso de implementación falla, el rollback El paso se activa automáticamente y restaura la versión anterior del Deployer.

GitLab CI/CD — canalización de implementación segura

yaml
# .gitlab-ci.yml
stages:
  - test
  - backup
  - deploy

variables:
  APP: myapp

.ssh: &ssh
  before_script:
    - apt-get install -y openssh-client
    - eval $(ssh-agent -s)
    - echo "$SERVER_SSH_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - ssh-keyscan -H "$SERVER_HOST" >> ~/.ssh/known_hosts

test:
  stage: test
  script: php artisan test
  only: [main]

backup-local:
  stage: backup
  <<: *ssh
  only: [main]
  script:
    - ssh root@$SERVER_HOST "cipi db backup $APP"

backup-s3:
  stage: backup
  <<: *ssh
  only: [main]
  script:
    - ssh root@$SERVER_HOST "cipi backup run $APP"

deploy:
  stage: deploy
  <<: *ssh
  only: [main]
  script:
    - |
      ssh root@$SERVER_HOST "
        cipi deploy $APP || {
          cipi deploy $APP --rollback
          echo 'Deploy failed — rolled back'
          exit 1
        }
      "
  after_script:
    - echo "Released → https://myapp.com"

backup-local y backup-s3 están en la misma etapa por lo que se ejecutan en paralelo si tiene varios corredores, lo que reduce el tiempo total de canalización. Ambos deben tener éxito antes de que deploy comienza la etapa.

Restaurar desde copia de seguridad local

Si necesita revertir la base de datos a la instantánea tomada justo antes de la implementación:

fiesta
# list available local snapshots
$ ls -lh /var/log/cipi/backups/myapp_*.sql.gz

# restore the most recent one
$ cipi db restore myapp /var/log/cipi/backups/myapp_20260303_143012.sql.gz

# also roll back the code release
$ cipi deploy myapp --rollback

Restaurar desde la copia de seguridad S3

fiesta
# list available S3 snapshots for this app
$ cipi backup list myapp

# download the DB snapshot from S3
$ aws s3 cp s3://your-bucket/cipi/myapp/2026-03-03_143015/db.sql.gz /tmp/db.sql.gz

# restore the database
$ cipi db restore myapp /tmp/db.sql.gz

# (optional) restore shared/ files
$ aws s3 cp s3://your-bucket/cipi/myapp/2026-03-03_143015/shared.tar.gz /tmp/shared.tar.gz
$ tar -xzf /tmp/shared.tar.gz -C /home/myapp/
Las copias de seguridad locales nunca se eliminan automáticamente. Cada implementación agrega una nueva .sql.gz archivo a /var/log/cipi/backups/. En un calendario de despliegue muy ocupado, agregue una limpieza cron o conserve solo los últimos N archivos:

ls -t /var/log/cipi/backups/myapp_*.sql.gz | tail -n +6 | xargs rm -f

Este ejemplo mantiene las 5 instantáneas más recientes y elimina las más antiguas.

Vista previa de entornos (implementación por sucursal)

Caso de uso de canalización: Los webhooks apuntan a una única URL de producción: no pueden generar una nueva Cipi aplicación por sucursal. Los entornos de vista previa requieren una CI/CD tubería que ingresa mediante SSH al servidor, calcula un nombre de aplicación determinista de la rama y corre cipi app create o cipi deploy respectivamente. Cada no producción La sucursal puede obtener su propia URL activa: una aplicación Laravel completamente implementada con su propia base de datos, trabajadores y HTTPS. Este patrón a veces se denomina "aplicaciones de revisión" o "entornos efímeros".

El formato URL utiliza tres barras separadas por guiones, por lo que cada entorno es legible por humanos y globalmente único:

URL de ejemplo
https://develop-acmeco-3a1f9c2e.preview.domain.ltd
https://release-1-2-3-acmeco-3a1f9c2e.preview.domain.ltd
https://main-acmeco-3a1f9c2e.preview.domain.ltd

Cómo se generan los identificadores

Se derivan tres valores en tiempo de ejecución de la canalización:

fiesta
# branch name → lowercase, non-alphanum → hyphens, trim edges
BRANCH_SLUG=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' \
  | sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//')

# repo/project name → same treatment
PROJECT_SLUG=$(echo "$PROJECT" | tr '[:upper:]' '[:lower:]' \
  | sed 's/[^a-z0-9]/-/g')

# deterministic MD5 hash — same branch always gets the same environment
HASH=$(echo -n "${BRANCH_SLUG}${PROJECT_SLUG}" | md5sum | cut -c1-8)

# Cipi app username: must be lowercase alphanumeric, 3–32 chars, no hyphens
# hex chars (0–9, a–f) are valid; prefix "pr" ensures it starts with a letter
APP_NAME="pr${HASH}"    # e.g. pr3a1f9c2e

# human-readable domain with wildcard base
DOMAIN="${BRANCH_SLUG}-${PROJECT_SLUG}-${HASH}.${DEPLOY_WILDCARD_DOMAIN}"

Requisitos previos (configuración única del servidor)

1. DNS comodín - agregar un A grabar *.preview.domain.ltd → <server-ip> en su proveedor DNS. Todos los subdominios resolver automáticamente; no se necesitan DNS cambios por rama.

2. Certificado comodín SSL — obtener un certificado comodín a través del desafío DNS-01 una vez e instalarlo en el servidor. Ver el Dominios comodín sección para obtener instrucciones. La ruta del certificado utilizado por los ejemplos de canalización a continuación es /etc/letsencrypt/live/preview.domain.ltd/.

3. Acceso al repositorio — los ejemplos de canalización utilizan una URL HTTPS con una dirección personal token de acceso (PAT) integrado, por lo que no se necesita configuración de clave de implementación SSH por aplicación. La ficha sólo necesita leer acceso al repositorio.

GitHub Acciones

Agregue estos secretos al repositorio: SERVER_HOST, SERVER_SSH_KEY, DEPLOY_WILDCARD_DOMAIN (por ej. preview.domain.ltd), GH_PAT (un PAT detallada con acceso de lectura al repositorio).

yaml
# .github/workflows/preview.yml
name: Preview

on:
  push:
    branches-ignore: [main, master]   # main branch uses your production pipeline
  delete:                              # clean up when a branch is deleted

jobs:
  deploy:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Compute identifiers
        id: ids
        run: |
          BRANCH_SLUG=$(echo "${{ github.ref_name }}" \
            | tr '[:upper:]' '[:lower:]' \
            | sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//')
          PROJECT_SLUG=$(echo "${{ github.event.repository.name }}" \
            | tr '[:upper:]' '[:lower:]' \
            | sed 's/[^a-z0-9]/-/g')
          HASH=$(echo -n "${BRANCH_SLUG}${PROJECT_SLUG}" | md5sum | cut -c1-8)
          APP_NAME="pr${HASH}"
          DOMAIN="${BRANCH_SLUG}-${PROJECT_SLUG}-${HASH}.${{ secrets.DEPLOY_WILDCARD_DOMAIN }}"
          REPO="https://oauth2:${{ secrets.GH_PAT }}@github.com/${{ github.repository }}.git"
          echo "app_name=${APP_NAME}"   >> "$GITHUB_OUTPUT"
          echo "domain=${DOMAIN}"       >> "$GITHUB_OUTPUT"
          echo "repo_url=${REPO}"       >> "$GITHUB_OUTPUT"

      - name: Create or update preview
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            APP="${{ steps.ids.outputs.app_name }}"
            DOMAIN="${{ steps.ids.outputs.domain }}"
            REPO="${{ steps.ids.outputs.repo_url }}"
            BRANCH="${{ github.ref_name }}"
            WILDCARD="/etc/letsencrypt/live/${{ secrets.DEPLOY_WILDCARD_DOMAIN }}"

            if cipi app show "$APP" &>/dev/null; then
              echo "→ Updating: $APP"
              cipi deploy "$APP"
            else
              echo "→ Creating: $APP → $DOMAIN"
              cipi app create \
                --user="$APP" \
                --domain="$DOMAIN" \
                --repository="$REPO" \
                --branch="$BRANCH" \
                --php=8.5

              # Patch nginx to listen on 443 using the pre-installed wildcard cert
              awk -v cert="$WILDCARD" '
                /^    listen 80;/ {
                  print
                  print "    listen 443 ssl http2;"
                  print "    ssl_certificate " cert "/fullchain.pem;"
                  print "    ssl_certificate_key " cert "/privkey.pem;"
                  next
                }
                { print }
              ' "/etc/nginx/sites-available/$APP" > /tmp/_cipi_vhost \
                && mv /tmp/_cipi_vhost "/etc/nginx/sites-available/$APP"
              nginx -t && systemctl reload nginx

              cipi deploy "$APP"
            fi

      - name: Print preview URL
        run: |
          echo ""
          echo "  Preview → https://${{ steps.ids.outputs.domain }}"
          echo ""

  cleanup:
    if: github.event_name == 'delete'
    runs-on: ubuntu-latest
    steps:
      - name: Compute identifiers
        id: ids
        run: |
          BRANCH_SLUG=$(echo "${{ github.event.ref }}" \
            | tr '[:upper:]' '[:lower:]' \
            | sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//')
          PROJECT_SLUG=$(echo "${{ github.event.repository.name }}" \
            | tr '[:upper:]' '[:lower:]' \
            | sed 's/[^a-z0-9]/-/g')
          HASH=$(echo -n "${BRANCH_SLUG}${PROJECT_SLUG}" | md5sum | cut -c1-8)
          echo "app_name=pr${HASH}" >> "$GITHUB_OUTPUT"

      - name: Delete preview
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: root
          key: ${{ secrets.SERVER_SSH_KEY }}
          script: |
            APP="${{ steps.ids.outputs.app_name }}"
            if cipi app show "$APP" &>/dev/null; then
              echo "y" | cipi app delete "$APP"
              echo "→ Deleted: $APP"
            else
              echo "→ Not found, nothing to delete"
            fi

GitLab CI/CD

Agregue estas CI/CD variables: SERVER_HOST, SERVER_SSH_KEY (tipo de archivo), DEPLOY_WILDCARD_DOMAIN, GL_TOKEN (un token de acceso al proyecto/grupo con repositorio_lectura alcance).

yaml
# .gitlab-ci.yml
stages:
  - preview
  - cleanup

.ssh_setup: &ssh_setup
  before_script:
    - apt-get install -y openssh-client
    - eval $(ssh-agent -s)
    - echo "$SERVER_SSH_KEY" | tr -d '\r' | ssh-add -
    - mkdir -p ~/.ssh
    - ssh-keyscan -H "$SERVER_HOST" >> ~/.ssh/known_hosts

.compute_ids: &compute_ids |
  BRANCH_SLUG=$(echo "$CI_COMMIT_REF_NAME" \
    | tr '[:upper:]' '[:lower:]' \
    | sed 's/[^a-z0-9]/-/g; s/--*/-/g; s/^-//; s/-$//')
  PROJECT_SLUG=$(echo "$CI_PROJECT_NAME" \
    | tr '[:upper:]' '[:lower:]' \
    | sed 's/[^a-z0-9]/-/g')
  HASH=$(echo -n "${BRANCH_SLUG}${PROJECT_SLUG}" | md5sum | cut -c1-8)
  APP="pr${HASH}"
  DOMAIN="${BRANCH_SLUG}-${PROJECT_SLUG}-${HASH}.${DEPLOY_WILDCARD_DOMAIN}"
  REPO="https://oauth2:${GL_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git"
  WILDCARD="/etc/letsencrypt/live/${DEPLOY_WILDCARD_DOMAIN}"

deploy-preview:
  stage: preview
  <<: *ssh_setup
  except:
    - main
    - master
  script:
    - *compute_ids
    - |
      ssh root@$SERVER_HOST bash -s << ENDSSH
        APP="$APP"
        DOMAIN="$DOMAIN"
        REPO="$REPO"
        BRANCH="$CI_COMMIT_REF_NAME"
        WILDCARD="$WILDCARD"

        if cipi app show "\$APP" &>/dev/null; then
          echo "Updating: \$APP"
          cipi deploy "\$APP"
        else
          echo "Creating: \$APP → \$DOMAIN"
          cipi app create \
            --user="\$APP" \
            --domain="\$DOMAIN" \
            --repository="\$REPO" \
            --branch="\$BRANCH" \
            --php=8.5

          awk -v cert="\$WILDCARD" '
            /^    listen 80;/ {
              print
              print "    listen 443 ssl http2;"
              print "    ssl_certificate " cert "/fullchain.pem;"
              print "    ssl_certificate_key " cert "/privkey.pem;"
              next
            }
            { print }
          ' "/etc/nginx/sites-available/\$APP" > /tmp/_cipi_vhost \
            && mv /tmp/_cipi_vhost "/etc/nginx/sites-available/\$APP"
          nginx -t && systemctl reload nginx

          cipi deploy "\$APP"
        fi
      ENDSSH
    - echo "Preview → https://$DOMAIN"

cleanup-preview:
  stage: cleanup
  <<: *ssh_setup
  only:
    - branches
  when: manual                    # or trigger on MR merge via rules:
  script:
    - *compute_ids
    - |
      ssh root@$SERVER_HOST "
        APP='$APP'
        if cipi app show \"\$APP\" &>/dev/null; then
          echo 'y' | cipi app delete \"\$APP\"
        fi
      "
En GitLab, puedes activar cleanup-preview automáticamente cuando se envía una solicitud de fusión. fusionados añadiendo un rules: bloque que comprueba $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train" o usando un dedicado workflow: con if: $CI_PIPELINE_SOURCE == "merge_request_event".

Notas y límites

Cada aplicación de vista previa es una aplicación Cipi completa — obtiene su propio usuario de Linux, base de datos, FPM grupo, Supervisor trabajador y crontab. En un VPS pequeño, esto se acumula rápidamente. correr cipi app list periódicamente y elimine las vistas previas obsoletas.

El parche nginx SSL no es idempotente — si el oleoducto corre cipi app create dos veces (por ejemplo, debido a un reintento), el awk parche será aplicado nuevamente. El hash asegura APP_NAME es determinista, por lo que if cipi app show guard evita la doble creación en condiciones normales.

evitar correr cipi ssl install en una aplicación de vista previa: lo hará sobrescriba la configuración del certificado comodín con un certificado Let's Encrypt por dominio que fallará (el El dominio no tiene un registro DNS dedicado, solo el comodín).