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>
114 lines
7.2 KiB
Markdown
114 lines
7.2 KiB
Markdown
## 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.yml` sobe apenas PostgreSQL; app local usa `artisan serve`.
|
||
- README declara PHP 8.5+; Docker/CI usam 8.4; Composer aceita `^8.3`.
|
||
- `composer quality` não roda `npm audit`; CI unit/feature usam `coverage: none` (SPEC §13.7 pede 80% Domain/Application).
|
||
- `User` bloqueia inativos em `canAccessPanel`, mas não implementa `MustVerifyEmail`; 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 `main` apó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 audit` e 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` / job `static`: adicionar `npm audit --omit=dev` (ou política documentada equivalente) após `npm ci` onde 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 a `app/Domain` e `app/Application` (SPEC §13.7). Views/migrations/framework fora.
|
||
|
||
### D7 — Auth parity
|
||
|
||
- `User` implementa `MustVerifyEmail`; `canAccessPanel` exige 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` + input `PRODUCTION`; 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
|
||
|
||
1. Fatia deploy: Compose deploy, workflows, smoke, trusted proxies, docs Dokploy/backup/rollback; CI verde.
|
||
2. Criar projeto Dokploy + Postgres (staging + produção) + Compose apps; registrar GHCR.
|
||
3. Primeiro push de imagem SHA → staging; smoke `/up` + home + login; testar rollback e backup/restore.
|
||
4. Promoção manual para produção após domínio/TLS/`APP_URL` confirmados.
|
||
5. Fatias restantes da change (auth/coverage/npm/Compose local/PHP docs) em PRs seguintes.
|
||
6. 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.update` env para `IMAGE_TAG` a cada deploy: preferir aliases `:staging`/`:production` estáveis no Compose Dokploy para evitar rewrite de env.
|