Por que FrankenPHP
Recomendação default pra Laravel novo no server-stack: FrankenPHP como base do Dockerfile. Comparação rápida:| Opção | Processos no container | Robustez | Complexidade do Dockerfile |
|---|---|---|---|
php artisan serve | 1 | ❌ single-thread, dev only | mínima |
| nginx + php-fpm + supervisord | 3 | ✅ batalha-testado | alta (3 configs + entrypoint) |
| FrankenPHP | 1 | ✅ stable, suporta workers Octane | mínima |
Dockerfile recomendado
SERVER_NAME=":80" faz o Caddy interno escutar HTTP-only na porta 80 — sem o auto-HTTPS do Caddy interferir, já que quem termina TLS é o NPM externamente.
TrustProxies obrigatório (Laravel 11+)
Se o projeto está em Laravel 11 ou 12, é obrigatório registrartrustProxies em bootstrap/app.php:
request()->isSecure() (que é false) e gera URLs com http:// — o browser então bloqueia CSS/JS por mixed content quando a página foi servida via HTTPS.
Em Laravel 10 ou anterior: o middleware vinha pré-configurado em app/Http/Middleware/TrustProxies.php no skeleton, então geralmente “simplesmente funciona” — mas vale conferir que $proxies = '*' está setado.
Por que '*' é seguro aqui: o container do projeto não publica portas no host (docker-compose.yml sem ports:). Os únicos containers que conseguem alcançar a porta 80 do projeto são os que estão na server_net — na prática, só o NPM. Defesa por isolamento de rede.
Variáveis de ambiente típicas
.env.example mínimo pra Laravel no server-stack:
APP_DEBUG=falsemesmo em “dev server” porque erros expostos vazam paths e config. Pra debug pontual, ligue temporariamente.SESSION/QUEUE/CACHEemdatabaseé simples e suficiente. Trocar praredisé optimization, não requirement.REDIS_HOST=redisaponta pro Redis compartilhado. Senha vem deinfra/redis/.env.
Migrations portáveis
UseSchema::Blueprint — funciona em MySQL, Postgres e SQLite igual. Evite DB::statement com SQL bruto.
Padrões portáveis:
Squash de migrations (quando vale)
Após muitas alterações de schema com refactors no caminho (renames, drops, pivots criados-e-removidos), a pastadatabase/migrations/ vira história arqueológica. Sintoma: ler todas as migrations não te diz o estado atual do schema.
Quando squashar:
- 15+ migrations com vários refactors
- Vai migrar pra um banco novo (oportunidade natural)
- Schema final é estável e tem chance baixa de mudar nas próximas semanas
- Schema está em fluxo ativo (squash hoje, refactor amanhã = trabalho perdido)
- Várias deploys em uso (cada um precisa do “manual migrations table populate” — overhead alto)
- Ler todas as migrations e mapear o estado final do schema
- Criar N novas migrations (4-6 normalmente) representando esse estado, organizadas por domínio
- Apagar as antigas
migrate:freshem DB limpo pra validar- Pra DBs existentes que tinham as antigas aplicadas, escrever um
database/squash-transition.sqlque substitui as entries da tabelamigrationspelas novas (sem re-rodar nada — schema já está aplicado)
Queue worker (quando aplicável)
Se o projeto usa Laravel Queues (php artisan queue:listen), separe em service distinto no docker-compose.yml:
Storage e uploads
Bind mount./storage:/app/storage pra persistir uploads, logs e cache de filesystem entre rebuilds.
php artisan storage:link deve estar no Dockerfile (build time), não em entrypoint. Senão perde no recreate. O Dockerfile recomendado acima já inclui esse RUN.
Comandos comuns
Tudo viadocker compose exec:
Páginas relacionadas
- Standards de projeto — convenções gerais (não-Laravel-specific)
- Criar bancos —
create-db.shpro Postgres compartilhado - Nginx Proxy Manager — configurar Proxy Host