Files
amare/openspec/changes/complete-foundation-parity/design.md
Manoel Freitas 7f4ea01f4b feat: prepare Dokploy staging and production deploy pipeline (#6)
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>
2026-08-01 23:40:13 -03:00

114 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Context
Fases 01 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.