cipi deploy

Cipi usa Implantador para todas as implantações. Cada implantação é atômica: uma nova versão diretório está totalmente preparado antes do current o link simbólico é trocado, então o tráfego nunca é interrompido.

Desde v4.5.4 Cipi pacotes Implantador 8, o que requer PHP ≥ 8,3. cipi deploy e cipi deploy --rollback abortar com uma mensagem clara de atualização antes invocando o Deployer quando um aplicativo ainda está fixado para uma versão PHP mais antiga - troque com cipi app edit <app> --php=8.3 (ou superior) primeiro.

Implantar pipeline

Deployer e Composer são executados com o versão PHP configurada do aplicativo (por exemplo /usr/bin/php8.5), não o padrão do sistema. Isto se aplica a cipi deploy, cipi deploy --rollback, gatilhos de implantação do crontab, cipi sync import implanta, e o deploy / composeraliases no usuário do aplicativo .bashrc.

  1. Parar trabalhadores da fila (cipi worker stop)
  2. Clonar repositório em releases/N/
  3. Corre composer install --no-dev (com PHP do aplicativo)
  4. Ligação shared/.env e shared/storage/
  5. Corre artisan migrate --force
  6. Corre artisan optimize
  7. Corre artisan storage:link
  8. Trocar current link simbólico atomicamente
  9. Reiniciar trabalhadores da fila
  10. Remova os lançamentos antigos (mantenha os últimos 5)
festa
$ 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, ativar pré-implantar snapshots de banco de dados estão disponíveis através --snapshot / --snapshot-required ou uma configuração permanente do aplicativo – consulte Pré-implantar snapshots de banco de dados. Os aplicativos Octane usam o laravel-octane.php Modelo do implantador (recarregar/reiniciar Octane na implantação); habilitar nó constrói com cipi app edit <app> --node-build='…'.

Se uma implantação for interrompida (por exemplo, por um erro de rede), o Deployer poderá deixar um arquivo de bloqueio para trás. Usar cipi deploy myapp --unlock para removê-lo antes de reimplantá-lo.

Pré-implantar snapshots de banco de dados

Desde v5.0, cipi deploy <app> pode despejar o banco de dados antes o pipeline de lançamento é executado. Instantâneos caem sob /var/log/cipi/backups/ - o mesmo caminho usado por cipi db backup.

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

O que acontece

  1. Antes de iniciar o Deployer, Cipi faz um dump do banco de dados /var/log/cipi/backups/.
  2. Com --snapshot-required, um dump com falha (ou um mecanismo de banco de dados ausente) blocos a implantação.
  3. Com --snapshot sozinho, um dump com falha imprime um aviso e a implantação continua.
--snapshotOpte pelo dump antes da implantação; avisar e continuar se o instantâneo não puder ser obtido.
--snapshot-requiredO mesmo dump, mas falha na implantação quando o snapshot (ou mecanismo) não está disponível.

Habilitar em cada implantação

Ative-o permanentemente para um aplicativo para que cada implantação (CLI, webhook ou pipeline) tire um snapshot primeiro:

festa
$ cipi app edit shop --predeploy-snapshot
cipi deploy --rollback restaura o anterior código liberar apenas. Isso acontece não restaurar o banco de dados. Se você precisar do dump pré-implantação de volta, use cipi db restore.

Para fluxos de trabalho de pipeline que também arquivam shared/ para S3 antes do lançamento, consulte Implantação segura – backup antes do lançamento.

cipi app deploy-config

Desde v5.0.3, gerencie opções de receitas duráveis do Deployer armazenadas em apps.json e aplicado regenerando deploy.php do modelo - uma alternativa segura para editar PHP de formato livre.

festa
$ 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 (habilidade apps-deploy-config, API 1.14+ / Cipi 5.0.3+). MCP: AppDeployConfigShow, AppDeployConfigUpdate.

auth.json

Gerenciar o auth.json arquivo para um aplicativo. Este arquivo reside em /home/<app>/shared/auth.json e é automaticamente vinculado a cada versão por Implantador - exatamente como .env. Use-o para armazenar dados de credenciais estruturados (por exemplo, API chaves, sinalizadores de recursos ou qualquer carga útil JSON) que seu aplicativo Laravel possa ler em tempo de execução.

festa
$ 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 (habilidade apps-auth, API 1.14+) — Composer/estruturado JSON, distinto de HTTP Autenticação Básica. MCP: AppAuthJsonShow, AppAuthJsonCreate, AppAuthJsonUpdate, AppAuthJsonDelete.

Detalhes do comando

Comando Descrição
cipi auth create <app> Cria shared/auth.json com a estrutura inicial {"users":[]}, define permissões para 640 (proprietário app:app) e acrescenta auth.json para shared_files na configuração do Deployer do aplicativo para que ele tenha um link simbólico em todos implantar.
cipi auth edit <app> Abre shared/auth.json em $EDITOR (volta para nano). Após o editor fechar, valida o JSON com jq e avisa se o arquivo está malformado.
cipi auth show <app> Imprime o conteúdo de shared/auth.json formatado com jq.
cipi auth delete <app> Pede confirmação e depois exclui shared/auth.json e remove o auth.json entrada de shared_files no Deployer do aplicativo configuração.

Integração do implantador

cipi auth create anexa automaticamente auth.json para o shared_files listar em /home/<app>/.deployer/deploy.phpe cipi auth delete remove-o. Isso significa que o arquivo é tratado exatamente como .env: persiste entre versões e nunca é substituído por uma implantação.

Cada cipi auth operação é registrada via log_action para auditabilidade. O AUTH seção também está listada na saída de cipi help.

Provedores Git

Cipi está pronto para trabalhar API e GitHub mas suporta qualquer outro provedor Git que oferece suporte a chaves de implantação SSH — sem dependência de fornecedor.

Para servidores self-hosted ou Git personalizados, você precisa confiar na impressão digital do host do servidor antes O implantador pode clonar por SSH. Use o --trust-host sinalizador para adicionar a impressão digital ao usuário do aplicativo ~/.ssh/known_hosts automaticamente:

festa
# 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
Quando uma porta não padrão é especificada, Cipi também grava o Host / Port entrada para o usuário do aplicativo ~/.ssh/config então que o Deployer possa acessar o servidor sem qualquer configuração extra.

Configuração automática do Git

Se você salvar um API ou GitHub Token de acesso pessoal, Cipi adiciona automaticamente a chave de implantação SSH e cria o webhook no repositório toda vez que você executacipi app create. Não são necessárias etapas manuais.

Salvar um token

festa
# 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 permissões de token

Tokens refinados (recomendados) precisam Administração e Webhooks definido como Ler e escrever nos repositórios de destino. Tokens clássicos preciso do repo escopo.

GitLab permissões de token

O api o escopo é o mínimo necessário — GitLab não oferece um escopo mais granular que abrange chaves de implantação e webhooks.

Ciclo de vida automático

Evento O que Cipi faz automaticamente
app create Adiciona chave de implantação + cria webhook no repositório via API. O resumo mostra "configurado automaticamente ✓" em vez de instruções manuais.
app edit --repository=... Remove a chave de implantação + webhook do repositório antigo e, em seguida, adiciona-os ao novo.
app delete Remove a chave de implantação + webhook do repositório antes de excluir o aplicativo.

cipi git comandos

Comando Descrição
cipi git status Mostrar status de conexão do provedor e detalhes de integração por aplicativo (ID da chave de implantação, webhook ID)
cipi git github-token <token> Salve um token de acesso pessoal GitHub
cipi git gitlab-token <token> Salve um token de acesso pessoal GitLab
cipi git gitlab-url <url> Defina o URL base para uma instância self-hosted GitLab
cipi git remove-github Remova o token GitHub armazenado
cipi git remove-gitlab Remova o token GitLab e o URL armazenados

Configuração manual (substituição)

A configuração automática é ignorada quando nenhum token é configurado, quando a chamada API falha (permissões erradas, repositório não encontrado, limite de taxa) ou quando o repositório está hospedado em um provedor diferente GitHub ou GitLab (por exemplo, Gitea, Forgejo, Bitbucket). Em todos esses casos, Cipi volta para o fluxo de trabalho manual e a criação do aplicativo prossegue normalmente.

Para configurar a chave de implantação e webhook manualmente:

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

Em seguida, adicione-os nas configurações do repositório do seu provedor:

  • Implantar chave — API: Configurações → Chaves de implantação → Adicionar chave de implantação; GitLab: Configurações → Repositório → Implantar chaves
  • Webhook — API: Configurações → Webhooks → Adicionar webhook; API: Configurações → Webhooks → Adicionar novo webhook. Defina o URL da carga útil e secreto para os valores mostrados por cipi deploy myapp --webhook
Se você remover um token de provedor após a criação dos aplicativos com configuração automática, Cipi não ser capaz de limpar chaves de implantação e webhooks ao excluir ou editar esses aplicativos. Um aviso é mostrado e você precisará removê-los manualmente das configurações do repositório do provedor.

Personalizando o script de implantação

A configuração de implantação de cada aplicativo é armazenada em:

/home/meu aplicativo/.deployer/deploy.php

Este arquivo é gerado automaticamente por Cipi durante app create e atualizado automaticamente quando você altere a versão PHP ou implante o branch via cipi app edit. Você pode editá-lo para personalizar o pipeline de implantação, mas você deve entender as implicações antes de fazer isso.

Pipeline de implantação padrão

O gerado automaticamente deploy.php executa estas tarefas em ordem:

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

Adicionando tarefas personalizadas

Você pode adicionar tarefas antes ou depois de qualquer etapa. Para um exemplo completo de construção de front-end (npm install && npm run build), veja Construindo ativos de front-end abaixo. Outros exemplos comuns:

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

Construindo ativos de front-end (npm / Vite)

Não há dedicado cipi Sinalizador CLI para compilações de front-end (por exemplo, npm install && npm run build). Personalização deploy.php é a abordagem apoiada e esperada - definir um Implantador task() e prendê-lo com after() ou before(). Você não precisa evitar isso; estender o pipeline é exatamente a finalidade do arquivo.

Cipi instalações Node.js e npm no servidor durante a configuração. Verifique se eles estão disponíveis como o usuário do aplicativo:

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

Confirmar package.json e package-lock.json para o seu repositório. Prenda a construção depois deploy:shared então .env está vinculado (Vite lê VITE_* variáveis ​​de lá) e antes deploy:symlink então ativos compilados existem na versão antes de ela ser lançada.

Anexe o bloco abaixo no inferior de /home/myapp/.deployer/deploy.php, abaixo das definições de tarefas geradas automaticamente 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');

Isso equivale a npm install && npm run build em cada implantação. Prefiro npm ci em produção quando package-lock.json está comprometido - é mais rápido e reproduzível. Usar npm install em vez disso, apenas se você não bloquear dependências.

Se você precisar de etapas separadas de instalação e compilação (por exemplo, para armazenar em cache node_modules entre lançamentos), divida-os em duas tarefas:

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 implantações subsequentes, você pode persistir dependências entre versões adicionando node_modules para os diretórios compartilhados do Deployer (opcional — somente se o seu projeto suportar isso):

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

Edite o arquivo no servidor como usuário do aplicativo e teste com cipi deploy myapp:

festa
$ 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 em GitHub ações ou GitLab CI antes a etapa de implantação do SSH, para que o servidor receba apenas ativos pré-construídos. Veja CI/CD pipelines – implantação SSH.

Executando comandos artisan adicionais

php
// Seed only in specific environments
task('artisan:db:seed', function () {
    run('{{bin/php}} {{release_path}}/artisan db:seed --force');
});
Cipi pode substituir a implantação.php quando você corre cipi app edit myapp --php=X ou cipi app edit myapp --branch=X. Backup suas personalizações ou mantê-las em uma seção claramente separada dos blocos gerenciados por Cipi. Um O padrão seguro é colocar todas as tarefas personalizadas na parte inferior do arquivo após a tarefa padrão definição.

Desativando uma etapa padrão

Para pular uma tarefa — por exemplo, se você lida com migrações manualmente — comente-a ou remova-a do deploy definição de tarefa:

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

Testando suas alterações

Depois de editar deploy.php, sempre faça uma implantação de teste antes de enviar para produção:

festa
$ 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
O log de implantação está sempre disponível em ~/logs/deploy.log ou através cipi app logs myapp --type=deploy. Verifique primeiro ao solucionar uma falha implantar.

Implantar e CI/CD — Visão geral

Com Cipi, CI (construir e testar) e CD (liberação para produção) pode ser dividido ou combinado. Em última análise, cada implantação executa o mesmo Implantador gasoduto no servidor - clone, composer install, migrações, troca de link simbólico, reinicialização do trabalhador. O que muda é o que desencadeia esse gasoduto.

Cipi oferece suporte a dois modelos de gatilho. Para a maioria dos aplicativos Laravel, comece com webhook + Cipi Agente caminho. Mude para um pipeline CI/CD completo quando precisar de portas, backups ou orquestração de infra-estrutura que um simples gancho não consegue expressar.

Duas maneiras de acionar uma implantação

Webhook + Cipi Agente (recomendado) CI/CD pipeline via SSH
Gatilho POSTs do provedor Git para /cipi/webhook ao pressionar GitHub Ações / GitLab execuções de trabalho de CI cipi deploy por SSH
Acesso ao servidor do CI Nenhum – apenas HTTPS para o domínio do seu aplicativo Chave SSH dedicada armazenada como segredo de CI
Testes pré-implantação Execute localmente ou em um trabalho de CI separado; implantar ainda dispara no push, a menos que você desabilite o webhook Nativo – a etapa de implantação é executada somente depois needs: test (ou equivalente) passes
Backup antes do lançamento Manual ou cron no servidor Trabalho de pipeline — consulte implantação segura
Visualizar/revisar aplicativos Não é compatível imediatamente Pipeline cria aplicativos Cipi por branch — consulte visualização ambientes
Complexidade de configuração Baixo - composer require cipi/agent + um webhook Médio — chave SSH, segredos, fluxo de trabalho YAML

Qual abordagem devo usar?

Caso de uso Abordagem recomendada Onde ler mais
Aplicativo Laravel único, push-to-deploy ativado main Webhook + Agente Webhook configuração
Implantar somente se os testes de CI forem aprovados Pipeline SSH (desativar produção webhook) Implantação de SSH de pipeline
Backup de banco de dados + arquivo antes de cada versão de produção Pipeline SSH Implantação segura com backup
Alertas do Slack/Telegram sobre o resultado da implantação Pipeline SSH Implantar notificações
URL efêmero por ramificação de recursos (avaliar aplicativos) Pipeline SSH Visualizar ambientes
Implante vários aplicativos em um servidor a partir de um repositório Ou - webhook por aplicativo ou um pipeline com paralelo cipi deploy Implantação de vários aplicativos
Escolha um gatilho por aplicativo. Não deixe uma produção webhook ativa enquanto também executando implantações de pipeline em push — duas execuções simultâneas do Deployer entram em conflito no arquivo de bloqueio. Usar cipi deploy myapp --unlock se um cadeado preso for deixado para trás.

Implantações automáticas — Cipi Agente e webhook

cipi-agente (cipi/agent) é um pacote Laravel que expõe POST /cipi/webhook dentro do seu aplicativo em execução. Quando GitHub ou GitLab envia um push evento, o agente valida a assinatura da carga útil, confirma imediatamente e enfileira uma implantação em o servidor - sem SSH do executor de CI, não sudo, nenhuma porta de entrada aberta além de HTTPS.

Como funciona o fluxo webhook

O design separa reconhecimento rápido de HTTP de Implantador lento trabalho. Uma implantação pode levar vários minutos; Provedores Git expiram chamadas webhook HTTP após ~10 segundos. Cipi resolve isso com um arquivo de sinalização e o crontab do usuário do aplicativo.

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

O Deployer sempre é executado como o usuário do aplicativo Linux (por exemplo myapp), com o corrigir permissões binárias e de arquivo PHP - o mesmo contexto de um manual cipi deploy myapp. O webhook nunca é pago diretamente ao Deployer; só deixa cair o arquivo de gatilho que o crontab de Cipi já monitora.

Pré-requisitos

Requisito Por que
Cipi aplicativo criado com repositório Git A chave de implantação deve clonar o repositório — consulte Configuração automática do Git
Pelo menos uma implantação manual bem-sucedida O pacote do agente deve estar presente no current lançamento antes de webhook rota existe
composer require cipi/agent no projeto Registra o /cipi/webhook validação de rota e assinatura
Webhook URL acessível através de HTTPS Os provedores Git exigem uma URL pública; usar cipi ssl install primeiro
CIPI_WEBHOOK_TOKEN em shared/.env Gerado automaticamente em cipi app create; compartilhado em todos os lançamentos

Configuração passo a passo

1. Crie o aplicativo e implante uma vez manualmente para que o servidor possa clonar seu repositório:

festa
$ 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 o Agente Cipi em seu projeto Laravel localmente, confirme e envie:

festa
$ 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 o webhook. Se você salvou um token GitHub ou GitLab, Cipi pode ter já criei o webhook durante app create - verifique com cipi git status. Caso contrário, recupere o URL e o segredo:

festa
$ cipi deploy myapp --webhook

Adicione webhook em seu provedor Git:

Provedor URL de carga útil Campo secreto Eventos
GitHub https://myapp.com/cipi/webhook Segredo → valor de --webhook Apenas o push evento
GitLab https://myapp.com/cipi/webhook Token secreto → mesmo valor Eventos push

4. Restrinja ao seu branch de implantação (recomendado para produção):

ambiente
CIPI_DEPLOY_BRANCH=main

Defina isso shared/.env através de cipi app env myapp. Empurra para outras filiais receber um skipped resposta e nenhuma implantação é executada.

5. Verifique. Envie um pequeno commit para main e observe o log de implantação:

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

Cerca de um minuto após a entrega de webhook, uma nova versão do Deployer deverá aparecer. Confirme o compromisso ao vivo com php artisan cipi:status ou o saúde verificar ponto final.

Configuração automática do Git

Quando um Token GitHub ou GitLab está configurado no servidor, Cipi registra a chave de implantação e cria o webhook automaticamente em cada cipi app create. O resumo do aplicativo mostra "configurado automaticamente ✓" em vez de manual instruções. Eventos do ciclo de vida (app edit --repository, app delete) manter chaves e webhooks sincronizados.

Solução de problemas

Sintoma Causa provável Correção
Webhook retorna 404 Agente ainda não implantado Corre cipi deploy myapp depois de adicionar cipi/agent para composer.json
Webhook retorna 403/assinatura inválida Incompatibilidade secreta Copiar novamente o token de cipi deploy myapp --webhook nas configurações do provedor
200 OK, mas sem implantação Filial filtrada Verifique CIPI_DEPLOY_BRANCH corresponde ao branch empurrado
Implantação travada/erro de bloqueio Implantação anterior interrompida cipi deploy myapp --unlock então tente novamente
A implantação é executada duas vezes em um push Webhook + pipeline ambos ativos Desative um gatilho - consulte visão geral
O agente também oferece suporte a implantações manuais e acionadas por IA por meio do MCP deploy ferramenta - ele usa o mesmo .deploy-trigger mecanismo. Veja Cipi Agente para verificações de integridade, MCP e recursos de anonimização.

CI/CD pipelines – implantação SSH

Quando o modelo webhook não for suficiente, execute GitHub Ações ou GitLab CI/CD jobs que fazem SSH no servidor e invocam cipi deploy. Este é o escolha certa sempre que a implantação deve ser condicional — fechado em testes, precedido por backups, seguido por notificações ou orquestrando novos aplicativos de visualização.

Quando você precisa de um pipeline em vez de um webhook

  • Portão de qualidade - correr php artisan test, análise estática ou front-end é compilado antes que qualquer código chegue à produção
  • Liberação segura - instantâneo do banco de dados e shared/ para S3 antes trocando o link simbólico (implantação segura)
  • Visibilidade da equipe - postar sucesso/falha no Slack ou Telegram com reversão ativada falha (implantar notificações)
  • Revise aplicativos — criar ou atualizar um aplicativo Cipi completo por ramificação de recursos (visualizar ambientes)
  • Monorepo para vários aplicativos - implantar frontend e api em paralelo após um único trabalho de teste

Para esses fluxos de trabalho, desabilitar a produção webhook (ou nunca crie um) então apenas os gatilhos do pipeline são implantados. Você ainda pode usar o agente Cipi no aplicativo para verificações de integridade e MCP.

Acesso SSH para CI

Gere um dedicado ed25519 par de chaves para o executor de CI. Adicione o chave pública para /root/.ssh/authorized_keys no servidor (ou no cipi usuário se preferir sudo cipi deploy) e armazene o chave privada como um segredo de CI. Nunca reutilize chaves de implantação do Git ou chaves SSH pessoais.
festa
# 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

Loja SERVER_HOST (IP do servidor ou nome do host) ao lado SERVER_SSH_KEY em seu segredos do repositório (GitHub) ou variáveis CI/CD (GitLab).

GitHub Ações – teste e implante

Adicione a chave privada como um segredo do repositório chamado SERVER_SSH_KEY e o IP do 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 reversão em caso de falha, estenda a etapa de script:

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

GitLab CI/CD

Adicione a chave privada como uma variável CI/CD chamada SERVER_SSH_KEY (tipo: Arquivo) e o servidor IP 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"

Com reversão em caso de falha:

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

Implantação de vários aplicativos

Se o mesmo pipeline gerenciar vários aplicativos no mesmo 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

Padrões avançados de pipeline

Depois que a implantação do SSH funcionar, componha estas seções em um único fluxo de trabalho de produção:

Padrão O que o pipeline adiciona Guia
Notificações Mensagem do Slack ou Telegram sobre sucesso, falha e reversão automática Implantar notificações
Implantação segura cipi db backup + cipi backup run antes cipi deploy; reversão em caso de falha Implantação segura com backup
Visualizar ambientes Crie/atualize/exclua aplicativos Cipi por ramificação com curinga DNS + SSL Visualizar ambientes

Uma configuração madura típica usa o webhook para um aplicativo de teste (feedback instantâneo sobre cada empurrar) e um gasoduto para produção (testes → backup → implantar → notificar). Cada aplicativo tem seu próprio gatilho - eles nunca entram em conflito porque têm como alvo diferentes usuários do aplicativo Cipi.

Implantar notificações

Caso de uso de pipeline: o caminho webhook é implantado silenciosamente — Git retorna 200 e a equipe só descobre se observarem os registros. Com um Pipeline SSH, adicione etapas de notificação apóscipi deploy para transmitir sucesso, fracasso e reversões para Slack ou Telegram. Ambos os exemplos abaixo funcionam com GitHub Actions e GitLab CI usando apenas chamadas HTTP padrão — sem dependências extras de plataforma.

Folga

Adicione uma etapa final que poste em um webhook do Slack, independentemente do resultado da implantação. Usar if: always() em GitHub Actions para que a notificação seja disparada em caso de sucesso e falha.

Crie um Entrada Webhook no seu espaço de trabalho do Slack e armazene o URL como SLACK_WEBHOOK_URL em seus segredos 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 CI GitLab, use curl diretamente — nenhum plugin é necessário:

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

Crie um bot do Telegram via @BotFather, obtenha o token do bot e encontre seu ID de bate-papo/grupo. Armazene-os como TELEGRAM_BOT_TOKEN e TELEGRAM_CHAT_ID em segredos 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, sem dependências extras):

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 seu ID de bate-papo do Telegram, adicione o bot ao grupo/canal alvo e ligue https://api.telegram.org/bot<TOKEN>/getUpdates e procure o chat.id campo na resposta. Para bate-papos privados, basta enviar uma mensagem ao bot primeiro.

Implantação segura – backup antes do lançamento

Para um dump integrado logo antes da execução do Deployer (sem necessidade de estágio de pipeline), use --snapshot / --snapshot-requiredou ativar cipi app edit <app> --predeploy-snapshot.

Caso de uso de pipeline: uma implantação webhook não pode executar uma etapa de backup antes de liberar o código — o evento push dispara a implantação imediatamente. Em um CI/CD pipeline, adicione um dedicado backup estágio que deve ter sucesso antes deploy começa. Um o fluxo de trabalho de nível de produção deve sempre criar um ponto de restauração antes o novo código vai viver. Cipi fornece dois comandos de backup complementares que mapeiam dois níveis de segurança diferentes:

festa
# 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 em um pipeline, eles fornecem um ponto de restauração local rápido e uma cópia fora do servidor do o banco de dados e todos os arquivos carregados. A implantação só será iniciada se ambos os backups forem bem-sucedidos.

Pré-requisito: correr cipi backup configure uma vez no servidor para vincule suas credenciais S3 antes cipi backup run pode ser usado. cipi db backup funciona sem qualquer configuração — está sempre disponível.

O que cada comando faz internamente

cipi db backup <app> chamadas mysqldump --single-transaction --routines --triggers e compacta a saída para /var/log/cipi/backups/<app>_<timestamp>.sql.gz. O arquivo permanece no servidor e nunca é excluído automaticamente — adicione uma etapa de limpeza ou um cron se o espaço em disco for importante.

cipi backup run <app> faz duas coisas: despeja o banco de dados com mariadb-dump --single-transaction em um diretório temporário e arquiva todo o /home/<app>/shared/ pasta (que contém .env, storage/e quaisquer arquivos enviados pelo usuário). Ambos os arquivos são então enviados para S3 sob o caminho cipi/<app>/<timestamp>/. Os arquivos temporários são excluídos após uma operação bem-sucedida carregar.

GitHub Ações — fluxo de trabalho de implantação seguro

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"

O gráfico de trabalho impõe a ordem: testbackupdeploy. Se qualquer trabalho falhar, os subsequentes serão ignorados. Se a etapa de implantação falhar, o rollback A etapa é acionada automaticamente e restaura a versão anterior do Deployer.

GitLab CI/CD — pipeline de implantação 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 e backup-s3 estão no mesmo estágio, então eles correm em paralelo se você tem vários executores, reduzindo o tempo geral do pipeline. Ambos devem ter sucesso antes do deploy fase começa.

Restaurar do backup local

Se você precisar reverter o banco de dados para o snapshot obtido logo antes da implantação:

festa
# 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 do backup S3

festa
# 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/
Os backups locais nunca são excluídos automaticamente. Cada implantação adiciona um novo .sql.gz arquivo para /var/log/cipi/backups/. Em uma agenda de implantação ocupada, adicione uma limpeza cron ou mantenha apenas os últimos N arquivos:

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

Este exemplo mantém os cinco instantâneos mais recentes e exclui os mais antigos.

Ambientes de visualização (implantação por filial)

Caso de uso de pipeline: webhooks apontam para um único URL de produção - eles não podem gerar um novo aplicativo Cipi por filial. Os ambientes de visualização exigem um CI/CD gasoduto que faz SSH no servidor, calcula um nome de aplicativo determinístico da ramificação e corre cipi app create ou cipi deploy de acordo. Toda não produção branch pode obter seu próprio URL ativo - um aplicativo Laravel totalmente implantado com seu próprio banco de dados, trabalhadores e HTTPS. Esse padrão às vezes é chamado de "aplicativos de avaliação" ou "ambientes efêmeros".

O formato da URL usa três slugs separados por hífens, para que cada ambiente seja legível e globalmente único:

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

Como os identificadores são gerados

Três valores são derivados no tempo de execução do pipeline:

festa
# 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é-requisitos (configuração única do servidor)

1. curinga DNS - adicione um A gravar *.preview.domain.ltd → <server-ip> em seu provedor DNS. Todos os subdomínios resolver automaticamente; nenhuma alteração DNS por branch é necessária.

2. Certificado curinga SSL — obtenha um certificado curinga por meio do desafio DNS-01 uma vez e instale-o no servidor. Veja o Domínios curinga seção para obter instruções. O caminho do certificado usado pelos exemplos de pipeline abaixo é /etc/letsencrypt/live/preview.domain.ltd/.

3. Acesso ao repositório — os exemplos de pipeline usam uma URL HTTPS com um nome pessoal token de acesso (PAT) incorporado, portanto, nenhuma configuração de chave de implantação SSH por aplicativo é necessária. O token só precisa leia acesso ao repositório.

GitHub Ações

Adicione estes segredos ao repositório: SERVER_HOST, SERVER_SSH_KEY, DEPLOY_WILDCARD_DOMAIN (por exemplo preview.domain.ltd), GH_PAT (um PAT refinado com acesso de leitura ao repositório).

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

Adicione estas variáveis CI/CD: SERVER_HOST, SERVER_SSH_KEY (Tipo de arquivo), DEPLOY_WILDCARD_DOMAIN, GL_TOKEN (um token de acesso de projeto/grupo com read_repositório escopo).

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
      "
Em GitLab, você pode acionar cleanup-preview automaticamente quando uma solicitação de mesclagem é mesclado adicionando um rules: bloquear que verifica $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train" ou usando um dedicado workflow: com if: $CI_PIPELINE_SOURCE == "merge_request_event".

Notas e limites

Cada aplicativo de visualização é um aplicativo Cipi completo - obtém seu próprio usuário Linux, banco de dados, FPM pool, trabalhador Supervisor e crontab. Em um VPS pequeno, isso se acumula rapidamente. Corre cipi app list periodicamente e exclua visualizações obsoletas.

O patch nginx SSL não é idempotente — se o pipeline for executado cipi app create duas vezes (por exemplo, devido a uma nova tentativa), o awk patch será aplicado novamente. O hash garante APP_NAME é determinístico, então o if cipi app show guarda impede a criação dupla em condições normais.

Evite correr cipi ssl install em um aplicativo de visualização - ele irá substituir a configuração do certificado curinga por um certificado Let's Encrypt por domínio que falhará (o domínio não possui registro DNS dedicado, apenas o curinga).