cipi deploy

Cipi utilisations Déployeur pour tous les déploiements. Chaque déploiement est atomique : une nouvelle version répertoire est entièrement préparé avant le current le lien symbolique est échangé, donc le trafic n'est jamais interrompu.

Depuis v4.5.4 Cipi lots Déployeur 8, ce qui nécessite PHP ≥ 8,3. cipi deploy et cipi deploy --rollback abandonner avec un message de mise à niveau clair avant appeler Deployer lorsqu'une application est toujours épinglée vers une ancienne version PHP — changez-la avec cipi app edit <app> --php=8.3 (ou supérieur) en premier.

Déployer un pipeline

Deployer et Composer s'exécutent avec le version PHP configurée de l'application (par ex. /usr/bin/php8.5), et non la valeur par défaut du système. Ceci s'applique à cipi deploy, cipi deploy --rollback, crontab déploie des déclencheurs, cipi sync import déploie, et le deploy / composer alias dans l'utilisateur de l'application .bashrc.

  1. Arrêter les travailleurs de file d'attente (cipi worker stop)
  2. Cloner le dépôt dans releases/N/
  3. Courir composer install --no-dev (avec le PHP de l'application)
  4. Lien shared/.env et shared/storage/
  5. Courir artisan migrate --force
  6. Courir artisan optimize
  7. Courir artisan storage:link
  8. Échange current lien symbolique atomiquement
  9. Redémarrer les travailleurs de file d'attente
  10. Élaguez les anciennes versions (conservez les 5 dernières)
coup
$ cipi deploy myapp              # deploy latest commit
$ cipi deploy myapp --rollback   # instant rollback to previous release
$ cipi deploy myapp --releases   # list releases with date, commit and subject (v5.1.0+)
$ cipi deploy myapp --log        # timestamped deploy log (v5.1.0+)
$ cipi deploy myapp --log=200    # last 200 lines of it
$ 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 --rollback-on-unhealthy  # v5.1.0+ undo a release that fails its healthcheck
$ 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)

Savoir si un déploiement a fonctionné (v5.1.0+)

Jusqu'à 5.0.x un déploiement défaillant pouvait passer sous silence : la branche défaillante, ses avertissements, le rollback indice et le deploy_fail l'e-mail était un code inaccessible, car une sortie non nulle de Le déployeur a tué le cipi processus sur place. Depuis v5.1.0:

  • Les deux chemins envoient des e-mails en cas de succès et d'échec — le CLI et le Git automatique webhook pareil. Les sujets sont visiblement différents (Cipi deploy succeeded: myapp release 55 … / Cipi deploy FAILED: myapp …), et le corps nomme la branche, le numéro de version, le hachage et le sujet du commit, son auteur et sa date, la durée, la version précédente, le déploiement chemin du journal et le verdict du contrôle de santé post-déploiement.
  • cipi deploy sort différent de zéro lorsque le déploiement a échoué, donc CI et les webhooks peuvent le voir. La même chose s'applique à cipi deploy --rollback.
  • L'e-mail de réussite est envoyé après vérification post-déploiement, donc ça ne pourra jamais annoncer un déploiement réussi alors que le site en renvoie 500.

Lecture du journal de déploiement

/home/<app>/logs/deploy.log était autrefois une sortie brute du déploiement ajoutée pour toujours, ce qui a rendu un déploiement qui a échoué du jour au lendemain, illisible par la suite. Depuisv5.1.0 chaque ligne est horodaté et chaque exécution est encadrée par une bannière nommant le déclencheur (CLI ou webhook), le branche, la sortie et la durée.

coup
$ cipi deploy myapp --log=100   # same as tailing /home/myapp/logs/deploy.log
$ cipi deploy myapp --releases  # release number, date, commit, subject

Les répertoires de versions restent numériques - la restauration dépend de cet ordre - donc --releases ajoute les détails humains par-dessus plutôt que de renommer quoi que ce soit.

Depuis v5.0, s'inscrire pré-déployer des instantanés de base de données sont disponibles via--snapshot / --snapshot-required ou un paramètre d'application permanent - voir Pré-déployer des instantanés de base de données. Octane applications utilisent le laravel-octane.php Modèle de déploiement (recharger/redémarrer Octane lors du déploiement) ; activer le nœud construit avec cipi app edit <app> --node-build='…'.

Si un déploiement est interrompu (par exemple par une erreur réseau), le déployeur peut laisser un fichier de verrouillage derrière lui. Utiliser cipi deploy myapp --unlock pour le supprimer avant de le redéployer.

Pré-déployer des instantanés de base de données

Depuis v5.0, cipi deploy <app> peut vider la base de données avant le pipeline de publication s'exécute. Les instantanés atterrissent sous/var/log/cipi/backups/ — le même chemin utilisé par cipi db backup.

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

Que se passe-t-il

  1. Avant le démarrage de Deployer, Cipi effectue un dump de base de données dans /var/log/cipi/backups/.
  2. Avec --snapshot-required, un dump échoué (ou un moteur de base de données manquant) blocs le déploiement.
  3. Avec --snapshot seul, un dump échoué imprime un avertissement et le déploiement continue.
--snapshotDump opt-in avant le déploiement ; avertir et continuer si l'instantané ne peut pas être pris.
--snapshot-requiredMême dump, mais le déploiement échoue lorsque l'instantané (ou le moteur) n'est pas disponible.

Activer à chaque déploiement

Activez-le de manière permanente pour une application afin que chaque déploiement (CLI, webhook ou pipeline) prenne d'abord un instantané :

coup
$ cipi app edit shop --predeploy-snapshot
cipi deploy --rollback restaure le précédent coder libération seulement. C'est le cas pas restaurer la base de données. Si vous avez besoin du dump de pré-déploiement, utilisez cipi db restore.

Pour les workflows de pipeline qui archivent également shared/ à S3 avant la libération, voir Déploiement sécurisé : sauvegarde avant la publication.

cipi app deploy-config

Depuis v5.0.3, gérez les options de recettes de déploiement durables stockées dans apps.json et appliqué en régénérant deploy.php à partir du modèle - une alternative sûre à l'édition de PHP de forme libre.

coup
$ 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

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

cipi.yml — configuration qui voyage avec le code

Disponible depuis v5.1.0. Une application peut transporter un cipi.yml fichier dans son référentiel décrivant l'état attendu : alias de domaine, version PHP et paramètres, bases de données supplémentaires, travailleurs de la file d'attente ou Horizon, Reverb, le planificateur, c'est bilan de santé et son sauvegarde stratégie. Le fichier réside à côté du code, la configuration du serveur est donc revue, versionné et expédié comme tout le reste.

Commandes

coup
$ cipi yml generate myapp          # this app's current config, as a cipi.yml
$ cipi yml example myapp           # blank commented template, in myapp's namespace
$ cipi yml validate myapp          # parse and check, change nothing
$ cipi yml plan myapp              # show exactly what would change
$ cipi yml apply myapp [--yes]     # apply it
$ cipi yml auto myapp on|off|status  # apply after every successful deploy

Le fichier est recherché dans current/cipi.yml, alors current/cipi.yaml, alors shared/cipi.yml — remplacer par --file=<path>.

Partez de ce que le serveur possède déjà

Vous n'êtes pas obligé de l'écrire à la main. cipi yml generate <app> prints the app's configuration as it stands on the server — aliases, PHP version and per-app settings, its extra databases, its queue workers (read back out of Supervisor), the scheduler and the backup profiles it owns — as a ready-to-commit file.

coup
$ cipi yml generate myapp > cipi.yml   # then commit it
$ cipi yml plan myapp                  # reports nothing to do

Cron expressions reviennent comme les plus conviviales every: 30mforme où ils mappent proprement, les valeurs sont citées partout où un simple scalaire pourrait être mal lu, et le résultat est transmis via le validateur avant impression. Les profils de sauvegarde à l'échelle du serveur et la propre base de données de l'application sont délibérément laissés de côté – ceux-ci restent à vous.

Le dossier

yaml
version: 1

app:
  # 8.3, 8.4 or 8.5 — must already be installed (cipi php install 8.5)
  php: "8.5"

  # The declared list replaces the current aliases: one you remove here is
  # removed from the server. The primary domain is not managed here.
  aliases:
    - "www.myapp.com"
    - "*.monapp.com"      # wildcard, for multi-tenant subdomains

  # Per-app php.ini overrides. Server-wide values stay with `cipi ini set`.
  ini:
    upload_max_filesize: 50M
    post_max_size: 60M
    memory_limit: 512M

# Extra databases beyond the one created with the app. Credentials land in
# /home/myapp/shared/cipi-databases.env — never written back to the repo.
databases:
  - name: myapp_reporting
  - name: myapp_analytics
    engine: pgsql          # mariadb (default) or pgsql

workers:
  horizon: false           # true replaces the queue workers below

  # Laravel Reverb (5.1.2+). Cipi allocates a localhost port, adds the Supervisor
  # program, proxies /app/{key} and /apps/{id}/… on this app's own domain, and
  # generates REVERB_APP_ID/KEY/SECRET plus the VITE_ copies in the .env. Laravel
  # apps only — declaring it on a --custom app is refused.
  reverb: false

  queues:
    - queue: default
      processes: 2
    - queue: emails
      processes: 1
      tries: 5
      timeout: 300

# Laravel scheduler (* * * * * artisan schedule:run)
schedule: true

# HTTP healthcheck. Probed every 5 minutes and right after every deploy.
# The URL must be one of this app's own domains.
health:
  url: "https://myapp.com/up"
  expect: 200
  # grace: 8                      # seconds before the first probe after a deploy
  # postdeploy: false             # skip the check right after a deploy
  # rollback_on_unhealthy: true   # undo a release that fails the check
  #                               # (the code symlink only — migrations are NOT undone)

# Backup strategy for this app. Profile names must be myapp or myapp-*.
backup:
  profiles:
    # Frequent and cheap: databases only, without the noisy tables.
    - name: myapp-db
      scope: db
      databases: ["monapplication", "monapplication_*", "locataire_*"]
      exclude_tables: ["*.emplois", "*.télescope_*"]
      every: 30m           # 5m/10m/15m/20m/30m, 1h..12h, 1d..28d
      keep: 48             # keep the last 48 runs
      destinations: [local]

    # Slower, complete, off-site and encrypted.
    - name: myapp-nightly
      scope: all           # all | files | db
      cron: "0 2 * * *"
      keep_days: 14
      destinations: [s3]
      encrypt: true
cipi yml example prend un nom d'application facultatif - cipi yml example myapp - Ainsi, les bases de données et les profils réservés atterrissent dans l'espace de noms de cette application et dans le modèle. valide tel quel.

Les déploiements ignorent le fichier jusqu'à ce que vous vous y inscriviez

Rien ne se passe lors du déploiement jusqu'à ce que vous exécutiez cipi yml auto <app> on. Avec cet opt-in donné, chaque réussi déployer des rapprochements - des deux cipi deploy et le Git webhook, ce dernier via une règle sudoers à portée étroite. Une version qui ne porte aucun cipi.yml est une opération silencieuse, et un fichier qui échoue à la validation est signalé par e-mail (yml_fail) et jamais partiellement appliqué. Un rapprochement réussi des incendies yml_apply.

Pourquoi il est prudent d'accepter via Git

Le fichier provient d'un référentiel, donc toute personne pouvant valider contrôle son contenu. C'est donc fermé en panne tout au long :

  • Cela ne peut que configurer une application qui existe déjà – ne jamais créer, renommer ou en supprimer un.
  • Ses bases de données doivent être nommées <app> ou <app>_*, et son profils de sauvegarde <app> ou <app>-*.
  • Son URL de contrôle de santé doit correspondre à l'un des domaines propres à l'application, sinon une validation pourrait viser l'enquêteur de cinq minutes du serveur à une adresse interne et relisez la réponse de l'alerte e-mails.
  • Les clés inconnues sont des erreurs et aucun champ ne contient de commande shell ou de chemin à inclure.
  • L'analyseur implémente un sous-ensemble YAML délibérément petit et refuse les ancres, les alias, les balises, les fusions. clés, scalaires de blocs et mappages de flux.
Une fois yml auto est activé, toute personne pouvant accéder à ce référentiel peut modifier le nom de l'application. alias, paramètres PHP, travailleurs, contrôle de santé et profils de sauvegarde. C'est le but de configuration-as-code - traite l'accès en écriture au dépôt en conséquence.

auth.json

Gérer le auth.json fichier pour une application. Ce fichier se trouve à/home/<app>/shared/auth.json et est automatiquement lié symboliquement à chaque version par Déployeur - exactement comme .env. Utilisez-le pour stocker des données d'identification structurées (par exemple API clés, indicateurs de fonctionnalité ou toute charge utile JSON) que votre application Laravel peut lire au moment de l'exécution.

coup
$ 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

REPOS : GET|POST|PUT|DELETE /api/apps/{name}/auth (capacité apps-auth, API 1.14+) — Composer/structuré JSON, distinct de HTTP Basic Auth. MCP : AppAuthJsonShow, AppAuthJsonCreate, AppAuthJsonUpdate, AppAuthJsonDelete.

Détails de la commande

Commande Descriptif
cipi auth create <app> Crée shared/auth.json avec la structure initiale {"users":[]}, définit les autorisations sur 640 (propriétaire app:app), et ajoute auth.json à shared_files dans la configuration du déploiement de l'application afin qu'il soit lié symboliquement à chaque déployer.
cipi auth edit <app> Ouvre shared/auth.json dans $EDITOR (retombe à nano). Après la fermeture de l'éditeur, valide le JSON avec jq et avertit si le fichier est mal formé.
cipi auth show <app> Imprime le contenu de shared/auth.json formaté avec jq.
cipi auth delete <app> Demande une confirmation, puis supprime shared/auth.json et supprime le auth.json entrée de shared_files dans le déployeur de l'application configuration.

Intégration du déployeur

cipi auth create ajoute automatiquement auth.json à la shared_files liste dans /home/<app>/.deployer/deploy.php, et cipi auth delete le supprime. Cela signifie que le fichier est traité exactement comme .env: il persiste dans toutes les versions et n'est jamais écrasé par un déploiement.

Chaque cipi auth l'opération est enregistrée via log_action pour auditabilité. Le AUTH La section est également répertoriée dans la sortie de cipi help.

Fournisseurs Git

Cipi est prêt à travailler avec GitHub et GitLab mais il prend en charge n'importe quel autre fournisseur Git qui prend en charge les clés de déploiement SSH – pas de dépendance vis-à-vis du fournisseur.

Pour les serveurs self-hosted ou Git personnalisés, vous devez faire confiance à l'empreinte digitale de l'hôte du serveur avant Le déployeur peut cloner via SSH. Utilisez le --trust-host drapeau pour ajouter l'empreinte digitale au utilisateur de l'application ~/.ssh/known_hosts automatiquement :

coup
# 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
Lorsqu'un port non standard est spécifié, Cipi écrit également le Host / Port accès à l'utilisateur de l'application ~/.ssh/config donc ce déployeur peut atteindre le serveur sans aucune configuration supplémentaire.

Configuration automatique de Git

Si vous enregistrez un GitHub ou GitLab Jeton d'accès personnel, Cipi ajoute automatiquement la clé de déploiement SSH et crée le webhook sur le référentiel à chaque fois que vous exécutezcipi app create. Aucune étape manuelle requise.

Enregistrer un jeton

coup
# 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 autorisations de jeton

Besoin de jetons à grain fin (recommandé) Administration et Webhooks réglé sur Lire et écrire sur les référentiels cibles. Jetons classiques besoin du repo portée.

GitLab autorisations de jeton

Le apila portée est le minimum requis — GitLab n'offre pas une portée plus granulaire qui couvre à la fois les clés de déploiement et les webhooks.

Cycle de vie automatique

Événement Ce que Cipi fait automatiquement
app create Ajoute la clé de déploiement + crée webhook sur le référentiel via API. Le résumé montre "auto-configuré ✓" au lieu d'instructions manuelles.
app edit --repository=... Supprime la clé de déploiement + webhook de l'ancien référentiel, puis les ajoute au nouveau.
app delete Supprime la clé de déploiement + webhook du référentiel avant de supprimer l'application.

cipi git commandes

Commande Descriptif
cipi git status Afficher l'état de connexion du fournisseur et les détails d'intégration par application (ID de clé de déploiement, webhook ID)
cipi git github-token <token> Enregistrez un jeton d'accès personnel GitHub
cipi git gitlab-token <token> Enregistrez un jeton d'accès personnel GitLab
cipi git gitlab-url <url> Définir l'URL de base pour une instance self-hosted GitLab
cipi git remove-github Retirez le jeton GitHub stocké
cipi git remove-gitlab Supprimez le jeton GitLab et l'URL stockés

Configuration manuelle (repli)

La configuration automatique est ignorée lorsqu'aucun jeton n'est configuré, lorsque l'appel API échoue (autorisations incorrectes, référentiel introuvable, limite de débit), ou lorsque le référentiel est hébergé chez un fournisseur autre que GitHub ou GitLab (par exemple Gitea, Forgejo, Bitbucket). Dans tous ces cas, Cipi revient au flux de travail manuel et la création de l'application se déroule normalement.

Pour configurer manuellement la clé de déploiement et webhook :

coup
# 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

Ajoutez-les ensuite dans les paramètres du référentiel de votre fournisseur :

  • Clé de déploiement — GitHub : Paramètres → Clés de déploiement → Ajouter une clé de déploiement; GitLab : Paramètres → Référentiel → Clés de déploiement
  • Webhook — GitHub : Paramètres → Webhooks → Ajouter webhook; GitLab : Paramètres → Webhooks → Ajouter un nouveau webhook. Définissez l'URL de la charge utile et secret des valeurs affichées par cipi deploy myapp --webhook
Si vous supprimez un jeton de fournisseur après la création d'applications avec la configuration automatique, Cipi ne sera pas être en mesure de nettoyer les clés de déploiement et les webhooks lorsque vous supprimez ou modifiez ces applications. Un avertissement est affichés et vous devrez les supprimer manuellement des paramètres du référentiel du fournisseur.

Personnalisation du script de déploiement

La configuration de déploiement pour chaque application est stockée à :

/home/monapplication/.deployer/deploy.php

Ce fichier est généré automatiquement par Cipi lors app create et mis à jour automatiquement lorsque vous changer la version PHP ou déployer la branche via cipi app edit. Vous pouvez le modifier pour le personnaliser le pipeline de déploiement, mais vous devez comprendre les implications avant de le faire.

Pipeline de déploiement par défaut

Le généré automatiquement deploy.php exécute ces tâches dans l'ordre :

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

Ajout de tâches personnalisées

Vous pouvez ajouter des tâches avant ou après n'importe quelle étape. Pour un exemple complet de build frontend (npm install && npm run build), voir Création d'actifs front-end ci-dessous. Autres exemples courants :

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');

Création d'actifs frontend (npm / Vite)

Il n'y a pas de dédié cipi CLI indicateur pour les builds frontend (par ex. npm install && npm run build). Personnalisation deploy.php est l’approche soutenue et attendue — définir un Déployeur task() et accroche-le avec after() ou before(). Vous ne le faites pas il faut éviter cela ; l'extension du pipeline est exactement à quoi sert le fichier.

Cipi installations Node.js et npm sur le serveur lors de l'installation. Vérifiez qu'ils sont disponibles comme l'utilisateur de l'application :

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

S'engager package.json et package-lock.json à votre référentiel. Accrochez la construction après deploy:shared donc .env est lié (Vite lit VITE_* variables à partir de là) et avantdeploy:symlink donc Les ressources compilées existent dans la version avant sa mise en ligne.

Ajoutez le bloc ci-dessous à la en bas de /home/myapp/.deployer/deploy.php, sous les définitions de tâches générées automatiquement par 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');

Cela équivaut à npm install && npm run build à chaque déploiement. Préférer npm ci en production lorsque package-lock.json est engagé - c'est plus rapide et reproductible. Utiliser npm install au lieu de cela seulement si vous ne verrouillez pas les dépendances.

Si vous avez besoin d'étapes d'installation et de construction distinctes (par exemple, pour mettre en cache node_modules à travers les versions), divisez-les en deux tâches :

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');

Pour accélérer les déploiements ultérieurs, vous pouvez conserver les dépendances entre les versions en ajoutant node_modules vers les répertoires partagés de Deployer (facultatif — uniquement si votre projet prend en charge ça):

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

Modifiez le fichier sur le serveur en tant qu'utilisateur de l'application, puis testez avec cipi deploy myapp:

coup
$ 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
Alternative : courir npm ci && npm run build dans GitHub Actions ou GitLab CI avant l'étape de déploiement SSH, de sorte que le serveur ne reçoit que des actifs prédéfinis. Voir CI/CD pipelines — Déploiement SSH.

Exécution de artisan commandes supplémentaires

php
// Seed only in specific environments
task('artisan:db:seed', function () {
    run('{{bin/php}} {{release_path}}/artisan db:seed --force');
});
Cipi peut écraser le déploiement.php quand tu cours cipi app edit myapp --php=X ou cipi app edit myapp --branch=X. Sauvegarder vos personnalisations ou conservez-les dans une section clairement séparée des blocs gérés par Cipi. Un le modèle sûr consiste à placer toutes les tâches personnalisées au bas du fichier après la tâche par défaut définition.

Désactiver une étape par défaut

Pour ignorer une tâche (par exemple si vous gérez les migrations manuellement), commentez-la ou supprimez-la du deploy définition de la tâche :

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',
]);

Tester vos modifications

Après édition deploy.php, effectuez toujours un test de déploiement avant de passer en production :

coup
$ 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
Le journal de déploiement est toujours disponible sur ~/logs/deploy.log ou via cipi app logs myapp --type=deploy. Vérifiez-le d'abord lors du dépannage d'un échec déployer.

Déployer et CI/CD — Présentation

Avec Cipi, CI (construire et tester) et CD (mise en production) peut être divisés ou combinés. Chaque déploiement fonctionne finalement de la même manière Déployeur canalisation sur le serveur — cloner, composer install, migrations, échange de liens symboliques, redémarrage du travailleur. Quels sont les changements qu'est-ce qui déclenche ce pipeline.

Cipi prend en charge deux modèles de déclencheurs. Pour la plupart des applications Laravel, commencez par le webhook + Cipi Agentchemin. Passez à un pipeline CI/CD complet lorsque vous avez besoin de portes, de sauvegardes ou une orchestration d'infrastructure qu'un simple push hook ne peut pas exprimer.

Deux façons de déclencher un déploiement

Webhook + Cipi Agent (recommandé) Pipeline CI/CD via SSH
Déclencheur Le fournisseur Git POST vers /cipi/webhook en poussée GitHub Actions / GitLab exécutions de tâches CI cipi deploy via SSH
Accès au serveur depuis CI Aucun — seulement HTTPS vers le domaine de votre application Clé SSH dédiée stockée en tant que secret CI
Tests préalables au déploiement Exécuter localement ou dans un travail CI distinct ; le déploiement se déclenche toujours en cas de poussée, sauf si vous désactivez le webhook Natif : l'étape de déploiement ne s'exécute qu'après needs: test (ou équivalent) passe
Sauvegarde avant la sortie Manuel ou cron sur le serveur Travail de pipeline - voir déploiement sécurisé
Prévisualiser/examiner les applications Non pris en charge dès la sortie de la boîte Pipeline crée Cipi applications par branche – voir aperçu environnements
Complexité de configuration Faible — composer require cipi/agent + un webhook Moyen — Clé SSH, secrets, workflow YAML

Quelle approche dois-je utiliser ?

Cas d'utilisation Approche recommandée Où lire la suite
Application Laravel unique, déploiement par simple pression sur main Webhook + Agent configuration Webhook
Déployer uniquement si les tests CI réussissent Pipeline SSH (désactiver la production webhook) Déploiement du pipeline SSH
Sauvegarde de base de données + fichiers avant chaque version de production Pipeline SSH Déploiement sécurisé avec sauvegarde
Alertes Slack / Telegram sur le résultat du déploiement Pipeline SSH Déployer les notifications
URL éphémère par branche de fonctionnalité (évaluer les applications) Pipeline SSH Environnements de prévisualisation
Déployer plusieurs applications sur un serveur à partir d'un seul référentiel Soit — webhook par application, soit un pipeline avec parallèlecipi deploy Déploiement multi-applications
Choisissez un déclencheur par application. Ne laissez pas un webhook de production actif alors que le pipeline en cours d'exécution se déploie lors du push : deux exécutions simultanées du déployeur sont en conflit sur le fichier de verrouillage. Utiliser cipi deploy myapp --unlock si un cadenas coincé est laissé sur place.

Déploiements automatiques — Cipi Agent & webhook

cipi-agent (cipi/agent) est un package Laravel qui expose POST /cipi/webhook dans votre application en cours d’exécution. Lorsque GitHub ou GitLab envoie un push événement, l'agent valide la signature de la charge utile, en accuse réception immédiatement et met en file d'attente un déploiement sur le serveur - pas de SSH du coureur CI, non sudo, aucun port entrant ouvert au-delà de HTTPS.

Comment fonctionne le flux webhook

Le design sépare acquittement rapide HTTP de Déployeur lent travail. Un déploiement peut prendre plusieurs minutes ; Les fournisseurs Git expirent webhook HTTP appels après environ 10 secondes. Cipi résout ce problème avec un fichier d'indicateur et la crontab de l'utilisateur de l'application.

flux
  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       │

Le déployeur s'exécute toujours en tant que utilisateur de l'application Linux (par ex. myapp), avec le corriger les autorisations PHP des binaires et des fichiers — le même contexte qu'un manuel cipi deploy myapp. Le webhook ne s'adresse jamais directement au déployeur ; ça laisse seulement tomber le fichier de déclenchement que la crontab de Cipi surveille déjà.

Conditions préalables

Exigence Pourquoi
Cipi application créée avec le référentiel Git La clé de déploiement doit cloner le dépôt - voir Configuration automatique de Git
Au moins un déploiement manuel réussi Le package d'agent doit être présent dans le current relâchez avant le webhook l'itinéraire existe
composer require cipi/agent dans le projet Enregistre le /cipi/webhook validation de l'itinéraire et de la signature
Webhook URL accessible sur HTTPS Les fournisseurs Git nécessitent une URL publique ; utiliser cipi ssl install d'abord
CIPI_WEBHOOK_TOKEN dans shared/.env Généré automatiquement à cipi app create; partagé dans toutes les versions

Configuration étape par étape

1. Créez l'application et déployez-la une fois manuellement pour que le serveur puisse cloner votre dépôt :

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

2. Installez l'agent Cipi dans votre Laravel projet localement, validez et poussez :

coup
$ 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. Configurez le webhook. Si vous avez enregistré un jeton GitHub ou GitLab, Cipi peut avoir déjà créé le webhook lors app create — vérifier auprès de cipi git status. Sinon, récupérez l'URL et le secret :

coup
$ cipi deploy myapp --webhook

Ajoutez le webhook dans votre fournisseur Git :

Fournisseur URL de la charge utile Champ secret Événements
GitHub https://myapp.com/cipi/webhook Secret → valeur de --webhook Juste le push événement
GitLab https://myapp.com/cipi/webhook Jeton secret → même valeur Événements push

4. Restreindre à votre branche de déploiement (recommandé pour la production) :

env
CIPI_DEPLOY_BRANCH=main

Mettez ceci dans shared/.env via cipi app env myapp. Pousse vers d’autres branches recevoir un skipped réponse et aucune exécution de déploiement.

5. Vérifiez. Envoyez un petit commit à main et regardez le journal de déploiement :

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

Environ une minute après la livraison webhook, une nouvelle version de Deployer devrait apparaître. Confirmez le commit en direct avec php artisan cipi:status ou le santé vérifier point final.

Configuration automatique de Git

Quand un Jeton GitHub ou GitLab est configuré sur le serveur, Cipi enregistre la clé de déploiement et crée automatiquement le webhook à chaquecipi app create. Le résumé de l'application affiche "auto-configuré ✓" au lieu du manuel instructions. Événements du cycle de vie (app edit --repository, app delete) garder clés et webhooks synchronisés.

Dépannage

Symptôme Cause probable Corriger
Webhook renvoie 404 Agent pas encore déployé Courir cipi deploy myapp après avoir ajouté cipi/agent à composer.json
Webhook renvoie 403 / signature invalide Inadéquation secrète Recopier le jeton de cipi deploy myapp --webhookdans les paramètres du fournisseur
200 OK mais pas de déploiement Branche filtrée Vérifier CIPI_DEPLOY_BRANCH correspond à la branche poussée
Déploiement bloqué/erreur de verrouillage Déploiement précédent interrompu cipi deploy myapp --unlock puis réessaye
Le déploiement s'exécute deux fois en une seule fois Webhook + pipeline tous deux actifs Désactivez un déclencheur - voir aperçu
L'agent prend également en charge les déploiements manuels et déclenchés par l'IA via le MCP deploy outil — il utilise la même chose .deploy-trigger mécanisme. Voir Cipi Agent pour les contrôles de santé, MCP et les fonctionnalités d'anonymisation.

CI/CD pipelines — Déploiement SSH

Lorsque le modèle webhook ne suffit pas, lancez GitHub Actions ou GitLab CI/CD tâches qui se connectent en SSH au serveur et invoquent cipi deploy. C'est le le bon choix chaque fois que le déploiement doit être conditionnel — gated sur tests, précédés de sauvegardes, suivi de notifications ou orchestrer de nouvelles applications en avant-première.

Quand vous avez besoin d'un pipeline au lieu d'un webhook

  • Porte de qualité — courir php artisan test, analyse statique ou frontend construit avant qu'un code n'atteigne la production
  • Libération en toute sécurité — instantané de la base de données et shared/ à S3 avant échanger le lien symbolique (déploiement sécurisé)
  • Visibilité de l'équipe - publier le succès/l'échec sur Slack ou Telegram avec restauration activée échec (déployer des notifications)
  • Examiner les applications — créer ou mettre à jour une Cipi application complète par branche de fonctionnalités (environnements de prévisualisation)
  • Monorepo multi-applications — déployer frontend et api dans parallèle après un seul travail de test

Pour ces flux de travail,désactiver la production webhook (ou ne jamais en créer un) donc seulement le pipeline déclenche le déploiement. Vous pouvez toujours utiliser Cipi Agent dans l'application pour les contrôles de santé et MCP.

Accès SSH pour CI

Générer un dédié ed25519 paire de clés pour le coureur CI. Ajoutez le clé publique à /root/.ssh/authorized_keys sur le serveur (ou le cipi utilisateur si vous préférez sudo cipi deploy) et stockez le clé privée comme un secret CI. Ne réutilisez jamais les clés de déploiement Git ou les clés SSH personnelles.
coup
# 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

Magasin SERVER_HOST(IP du serveur ou nom d'hôte) à côté SERVER_SSH_KEY dans votre secrets du référentiel (GitHub) ou CI/CD variables (GitLab).

GitHub Actions — tester puis déployer

Ajoutez la clé privée en tant que secret du référentiel nommé SERVER_SSH_KEY et l'IP du serveur comme 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

Pour une restauration en cas d'échec, étendez l'action de script :

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

GitLab CI/CD

Ajoutez la clé privée en tant que variable CI/CD nommée SERVER_SSH_KEY (tapez : Fichier) et le serveur IP comme 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"

Avec rollback en cas d'échec :

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

Déploiement multi-applications

Si le même pipeline gère plusieurs applications sur le même serveur :

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

Modèles de pipeline avancés

Une fois le déploiement SSH fonctionnel, composez ces sections en un seul workflow de production :

Modèle Ce que le pipeline ajoute Guider
Notifications Message Slack ou Telegram sur le succès, l'échec et la restauration automatique Déployer les notifications
Déploiement sécurisé cipi db backup + cipi backup run avant cipi deploy; restauration en cas d'échec Déploiement sécurisé avec sauvegarde
Environnements de prévisualisation Créer/mettre à jour/supprimer Cipi applications par branche avec le caractère générique DNS + SSL Environnements de prévisualisation

Une configuration mature typique utilise le webhook pour une application de mise en scène (retour instantané sur chaque pousser) et un canalisation pour la production (tests → sauvegarde → déployer → notifier). Chaque application a leur propre déclencheur : ils ne sont jamais en conflit car ils ciblent différents utilisateurs d'applications.

Déployer les notifications

Cas d'utilisation du pipeline : le chemin webhook se déploie silencieusement — Git renvoie 200 et l'équipe ne le découvre que s'ils regardent les journaux. Avec un Pipeline SSH, ajouter étapes de notification après cipi deploy pour diffuser le succès, l'échec et l'automatique retours vers Slack ou Telegram. Les deux exemples ci-dessous fonctionnent avec GitHub Actions et GitLab CI en utilisant uniquement des appels HTTP standard — pas de dépendances de plate-forme supplémentaires.

Mou

Ajoutez une étape finale qui publie sur un Slack webhook quel que soit le résultat du déploiement. Utiliser if: always() dans GitHub Actions afin que la notification se déclenche à la fois en cas de succès et d'échec.

Créer un Entrant Webhookdans votre espace de travail Slack et stockez l'URL sous SLACK_WEBHOOK_URLdans vos secrets 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

Pour GitLab CI, utilisez curl directement — aucun plugin n'est nécessaire :

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

Télégramme

Créez un bot Telegram via @BotPère, récupérez le jeton du bot et trouvez votre identifiant de discussion/groupe. Conservez-les comme TELEGRAM_BOT_TOKEN et TELEGRAM_CHAT_ID dans les secrets 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 équivalent CI (pur curl, pas de dépendances supplémentaires) :

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
Pour trouver votre identifiant de chat Telegram, ajoutez le bot au groupe/canal cible, puis appelez https://api.telegram.org/bot<TOKEN>/getUpdates et cherche le chat.id champ dans la réponse. Pour les discussions privées, envoyez d’abord un message au bot.

Déploiement sécurisé : sauvegarde avant la publication

Pour un vidage intégré juste avant l'exécution de Deployer (aucune étape de pipeline requise), utilisez --snapshot / --snapshot-required ou activer cipi app edit <app> --predeploy-snapshot.

Cas d'utilisation du pipeline : un déploiement webhook ne peut pas exécuter une étape de sauvegarde avant de publier le code — l'événement push se déclenche immédiatement. Dans un CI/CD canalisation, ajoutez un dédié backup étape qui doit réussir avant deploy démarre. Un le flux de travail de qualité production doit toujours créer un point de restauration avant le nouveau code va vivre. Cipi fournit deux commandes de sauvegarde complémentaires qui correspondent à deux niveaux de sécurité différents :

coup
# 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

Utilisés ensemble dans un pipeline, ils vous offrent à la fois un point de restauration local rapide et une copie hors serveur de la base de données et tous les fichiers téléchargés. Le déploiement ne démarre que si les deux sauvegardes réussissent.

Pré-requis : courir cipi backup configure une fois sur le serveur pour associez vos S3 identifiants avant cipi backup run peut être utilisé. cipi db backup fonctionne sans aucune configuration — il est toujours disponible.

Ce que fait chaque commande en interne

cipi db backup <app> appels mysqldump --single-transaction --routines --triggers et compresse la sortie dans /var/log/cipi/backups/<app>_<timestamp>.sql.gz. Le fichier reste sur le serveur et n'est jamais supprimé automatiquement — ajoutez une étape de nettoyage ou un cron si l'espace disque est important.

cipi backup run <app> fait deux choses : vide la base de données avec mariadb-dump --single-transaction dans un répertoire temporaire et archive l'intégralité /home/<app>/shared/ dossier (qui contient .env, storage/, et tous les fichiers téléchargés par l'utilisateur). Les deux archives sont ensuite téléchargées sur S3 sous le nom chemin cipi/<app>/<timestamp>/. Les fichiers temporaires sont supprimés après une réussite télécharger.

GitHub Actions — workflow de déploiement sécurisé

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"

Le graphique de tâches applique l'ordre : testbackupdeploy. Si toute tâche échoue, les suivantes sont ignorées. Si l'étape de déploiement elle-même échoue, le rollback L’étape se déclenche automatiquement et restaure la version précédente de Deployer.

GitLab CI/CD — pipeline de déploiement sécurisé

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 et backup-s3 sont dans la même étape donc ils fonctionnent en parallèle si vous disposez de plusieurs exécuteurs, ce qui réduit le temps global du pipeline. Les deux doivent réussir avant que deploy l'étape commence.

Restaurer à partir d'une sauvegarde locale

Si vous devez restaurer la base de données vers l'instantané pris juste avant le déploiement :

coup
# 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

Restaurer à partir de la sauvegarde S3

coup
# 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/
Les sauvegardes locales ne sont jamais supprimées automatiquement. Chaque déploiement ajoute un nouveau .sql.gz fichier à /var/log/cipi/backups/. Avec un calendrier de déploiement chargé, ajoutez un nettoyage cron ou ne conservez que les N derniers fichiers :

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

Cet exemple conserve les 5 instantanés les plus récents et supprime les plus anciens.

Environnements de prévisualisation (déploiement par branche)

Cas d'utilisation du pipeline :les webhooks pointent vers une seule URL de production – ils ne peuvent pas créer de Cipi nouvelles applications par agence. Les environnements de prévisualisation nécessitent un CI/CD canalisation qui se connecte en SSH au serveur, calcule un nom d'application déterministe à partir de la branche et courtcipi app create ou cipi deploy par conséquent. Chaque non-production La branche peut obtenir sa propre URL en direct : une application Laravel entièrement déployée avec sa propre base de données, ses propres travailleurs et ses propres ressources. HTTPS. Ce modèle est parfois appelé « évaluer les applications » ou « environnements éphémères ».

Le format URL utilise trois slugs séparés par des tirets, afin que chaque environnement soit lisible par l'homme et unique au monde :

exemples d'URL
https://develop-acmeco-3a1f9c2e.preview.domain.ltd
https://release-1-2-3-acmeco-3a1f9c2e.preview.domain.ltd
https://main-acmeco-3a1f9c2e.preview.domain.ltd

Comment les identifiants sont générés

Trois valeurs sont dérivées lors de l'exécution du pipeline :

coup
# 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}"

Prérequis (configuration unique du serveur)

1. Caractère générique DNS — ajouter un A enregistrer *.preview.domain.ltd → <server-ip> chez votre fournisseur DNS. Tous les sous-domaines résoudre automatiquement ; aucune modification DNS par branche n'est nécessaire.

2. Certificat Wildcard SSL — obtenir un certificat wildcard via le défi DNS-01 une fois et installez-le sur le serveur. Voir le Domaines génériques section pour les instructions. Le chemin du certificat utilisé par les exemples de pipeline ci-dessous est /etc/letsencrypt/live/preview.domain.ltd/.

3. Accès au référentiel — les exemples de pipeline utilisent une URL HTTPS avec un nom personnel jeton d'accès (PAT) intégré, donc aucune configuration de clé de déploiement SSH par application n'est nécessaire. Le jeton n'a besoin que lire accès au référentiel.

GitHub Actions

Ajoutez ces secrets au référentiel : SERVER_HOST, SERVER_SSH_KEY, DEPLOY_WILDCARD_DOMAIN (par ex. preview.domain.ltd), GH_PAT (un PAT à granularité fine avec accès en lecture au dépôt).

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

Ajoutez ces CI/CD variables : SERVER_HOST, SERVER_SSH_KEY (Type de fichier), DEPLOY_WILDCARD_DOMAIN, GL_TOKEN (un jeton d'accès au projet/groupe avec read_repository portée).

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, vous pouvez déclencher cleanup-preview automatiquement lorsqu'une demande de fusion est fusionné en ajoutant un rules: bloc qui vérifie $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"ou en utilisant un workflow: avec if: $CI_PIPELINE_SOURCE == "merge_request_event".

Remarques et limites

Chaque application en aperçu est une application Cipi complète — il obtient son propre utilisateur Linux, sa base de données et son FPM pool, Supervisor Worker et crontab. Sur un petit VPS, cela s’accumule rapidement. Courircipi app list périodiquement et supprimez les aperçus obsolètes.

Le patch nginx SSL n’est pas idempotent — si le pipeline fonctionne cipi app create deux fois (par exemple suite à une nouvelle tentative), le awk le patch sera appliqué à nouveau. Le hachage assure APP_NAME est déterministe, donc le if cipi app show La protection empêche la double création dans des conditions normales.

Évitez de courir cipi ssl install sur une application de prévisualisation - ce sera remplacer la configuration du certificat générique par un certificat Let's Encrypt par domaine qui échouera (le le domaine n'a pas d'enregistrement DNS dédié, seulement le caractère générique).