Publish immutable FrankenPHP images to GHCR, auto-deploy staging after CI, and promote the same digest to production with smoke, backup, and rollback docs. Co-authored-by: Cursor <cursoragent@cursor.com>
7.2 KiB
Context
Fases 0–1 e provedores de produção (Resend + R2) estão em main com CI verde. OpenSpec ativo foi limpo: specs promovidas e changes arquivadas. Gaps remanescentes da Fase 0:
- Critério de saída §18 exige hello-world em staging; só existe workflow CI.
docker-compose.ymlsobe apenas PostgreSQL; app local usaartisan serve.- README declara PHP 8.5+; Docker/CI usam 8.4; Composer aceita
^8.3. composer qualitynão rodanpm audit; CI unit/feature usamcoverage: none(SPEC §13.7 pede 80% Domain/Application).Userbloqueia inativos emcanAccessPanel, mas não implementaMustVerifyEmail; reset de senha Filament não está validado por teste.- Destino de staging escolhido: VPS própria com Dokploy (não Railway).
Goals / Non-Goals
Goals:
- Staging em Dokploy com a mesma imagem FrankenPHP por SHA para web, queue e scheduler.
- Deploy automático em
mainapós CI: build → GHCR → Dokploy API → migrate → health → smoke. - Produção via promoção manual da mesma digest SHA (alias
:production), sem rebuild. - Backup PostgreSQL diário (retenção ≥14 dias), restore documentado e rollback por SHA.
- Compose local com app FrankenPHP + Postgres (pendente fora da fatia deploy).
- PHP 8.4 canônico em docs/Docker/CI (pendente).
- Gates com
npm audite cobertura Domain/Application ≥ 80% (pendente). - E-mail verificado + reset de senha seguros no painel (ADM-01 / SPEC §12.1) (pendente).
- Strict types em PHP próprio faltante (pendente).
Non-Goals:
- Deploy automático em produção; provisionamento de VPS para clientes.
- Fase 2 (briefing/CRM/E2E-01/02).
- Redis, worker mode, CDN automation, signed private media.
Decisions
D1 — Dokploy Compose na VPS, imagem imutável no GHCR
GitHub Actions (após CI verde em main) constrói uma imagem ghcr.io/<owner>/<repo>:<git-sha> (+ alias :staging), faz push privado e chama POST /api/compose.deploy no Dokploy com x-api-key.
Compose compartilhado (docker-compose.deploy.yml) referencia ${APP_IMAGE} / IMAGE_TAG para web, queue, scheduler e job migrate one-shot (php artisan migrate --force). Dokploy mantém duas stacks Compose (staging e production) com IMAGE_TAG distinto. PostgreSQL é serviço Dokploy separado por ambiente (não na imagem da app).
Produção: workflow_dispatch com SHA + confirmação PRODUCTION move alias :production para o mesmo digest já publicado e dispara compose.deploy na stack de produção. Repo privado no GitHub Free não tem required reviewers de Environment; aprovação humana = disparo manual explícito (GitHub Pro opcional depois).
Alternativas rejeitadas: Railway (Pro para GHCR privado + desvio de plataforma); rebuild por serviço no Dokploy (quebra “mesma imagem” SPEC §14.3/§15.2); tag só :latest (rollback frágil); deploy automático direto em produção.
D2 — Processos e health
| Serviço | Comando |
|---|---|
| web | entrypoint padrão FrankenPHP (CMD da imagem) |
| queue | php artisan queue:work --sleep=2 --tries=3 |
| scheduler | php artisan schedule:work |
| migrate | one-shot antes/ao lado do deploy |
Healthcheck Docker/Dokploy e smoke pós-deploy usam GET /up (sem auth, sem secrets). Smoke mínimo: /up, home pública /, /admin/login respondem 200.
D3 — Secrets e providers
GitHub Actions guarda só orquestração: DOKPLOY_URL, API key, compose IDs, URLs públicas de smoke. Env Laravel (APP_KEY, DB, Resend, R2) vive somente no Dokploy, isolado por ambiente. Nunca embeds em layer. Local continua MAIL_MAILER=log e disco public.
D3b — Backup e restore
PostgreSQL staging/produção: backup diário via Dokploy → destino S3-compatible, retenção mínima 14 dias (SPEC §16.3). Restore documentado e testado em staging antes da primeira promoção a produção.
D4 — Compose local com app
Adicionar serviço app (build do Dockerfile) dependendo de postgres healthy, porta 8000, volumes só para storage local se útil. Documentar docker compose up como caminho preferido; artisan serve permanece opcional para iteração rápida sem rebuild.
D5 — PHP 8.4 canônico
Alinhar README, CI (setup-php 8.4) e Dockerfile ARG PHP_VERSION=8.4. Manter composer.json ^8.3 (compatibilidade de instalação), mas documentação e runtime canônicos = 8.4.
D6 — Quality gates
composer quality/ jobstatic: adicionarnpm audit --omit=dev(ou política documentada equivalente) apósnpm cionde assets forem necessários; falha bloqueia merge.- Jobs
unit(e, se aplicável, coverage dedicada): habilitar cobertura e falhar se Domain+Application < 80%. Escopo limitado aapp/Domaineapp/Application(SPEC §13.7). Views/migrations/framework fora.
D7 — Auth parity
UserimplementaMustVerifyEmail;canAccessPanelexige ativo e e-mail verificado.- Seed local marca admin/assistant como verificados.
- Habilitar fluxo de reset Filament/Laravel; feature tests: unverified denied, verified ok, reset request não revela existência de e-mail.
- Correção pontual de
declare(strict_types=1);em providers/arquivos próprios faltantes.
D8 — Rollback
Rollback = mover alias do ambiente (:staging ou :production) para tag SHA anterior no GHCR e compose.deploy. Falha de healthcheck/smoke impede promoção. Sem rebuild.
D9 — Trusted proxies atrás do Traefik
bootstrap/app.php confia em proxies (trustProxies(at: '*')) para honrar X-Forwarded-* do Traefik/Dokploy. Staging/produção usam SESSION_SECURE_COOKIE=true com HTTPS.
Risks / Trade-offs
- [GHCR privado + pull na VPS] → configurar registry no Dokploy com PAT
read:packages; documentar checklist. - [Migrate one-shot falha] → web/queue/scheduler dependem de migrate exit 0; manter migrations backward-compatible.
- [GitHub Free sem required reviewers] → promoção humana via
workflow_dispatch+ inputPRODUCTION; Pro opcional. - [Cobertura 80% com Domain quase vazio] → medir só namespaces existentes; baseline sobe conforme Fase 2 adiciona Domain (pendente).
- [npm audit ruido] →
--omit=dev+ allowlist documentada se necessário (pendente). - [E-mail verification em staging] → seed/users de staging com
email_verified_at; Resend para reset real quando configurado (pendente). - [Compose local rebuild lento] → documentar serve opcional; CI permanece fonte FrankenPHP (pendente).
Migration Plan
- Fatia deploy: Compose deploy, workflows, smoke, trusted proxies, docs Dokploy/backup/rollback; CI verde.
- Criar projeto Dokploy + Postgres (staging + produção) + Compose apps; registrar GHCR.
- Primeiro push de imagem SHA → staging; smoke
/up+ home + login; testar rollback e backup/restore. - Promoção manual para produção após domínio/TLS/
APP_URLconfirmados. - Fatias restantes da change (auth/coverage/npm/Compose local/PHP docs) em PRs seguintes.
- Atualizar SPEC §18 Fase 0 apenas com itens comprovados; evidência no PR.
Open Questions
- Domínio público exato do staging/produção (DNS) — preencher na implementação com valor do operador.
- Se Dokploy Compose API exigir
compose.updateenv paraIMAGE_TAGa cada deploy: preferir aliases:staging/:productionestáveis no Compose Dokploy para evitar rewrite de env.