Deploy (Hostinger VPS) com Dokploy
Documentação KOLOS

Deploy (Hostinger VPS) com Dokploy

Matriz dev / CI / Docker único / Compose: environments-matrix.md.

Este runbook cobre duas formas de deploy no Dokploy:

  1. Dockerfile (recomendado): garante PHP 8.5 e Node 24, alinhado com composer.json/Vite.
  2. Docker Compose: útil quando você quer subir pgsql/redis/reverb juntos.

Nota importante: o builder Nixpacks do Dokploy atualmente cai para PHP 8.3 e Node 18 (pelos logs), o que é incompatível com este projeto (requer php ^8.5.1 e Vite exige Node >= 20.19).

1) DNS

  • A @ → IP da VPS
  • (Opcional) A www → IP da VPS
  • (Recomendado para Reverb) A ws → IP da VPS

2) Criar o App no Dokploy

  • Provider: GitHub
  • Repo: seu repositório
  • Branch: main
  • Build path: /
  • Trigger: On Push

3) Deploy via Docker Compose

Use o arquivo de produção:

  • Compose file: compose.dokploy.yaml
  • Main service/port: app na porta 80

Se o Dokploy permitir expor múltiplos serviços, exponha também:

  • reverb na porta 8080 em ws.seu-dominio.com

4) Variáveis de ambiente (Dokploy)

Laravel

  • APP_ENV=production
  • APP_DEBUG=false
  • APP_URL=https://seu-dominio.com
  • APP_KEY=base64:... (gerar uma vez com php artisan key:generate --show)

Se você não conseguir rodar o artisan no container, gere localmente e cole no Dokploy:

  • php -r "echo 'base64:'.base64_encode(random_bytes(32)).PHP_EOL;"

Banco (Postgres no Compose)

  • DB_CONNECTION=pgsql
  • DB_HOST=pgsql
  • DB_PORT=5432
  • DB_DATABASE=kolos (ou o que você definir)
  • DB_USERNAME=kolos
  • DB_PASSWORD=...

Redis

  • REDIS_HOST=redis
  • REDIS_PORT=6379
  • (Recomendado) CACHE_STORE=redis
  • (Recomendado) QUEUE_CONNECTION=redis

Reverb (WebSockets)

  • BROADCAST_CONNECTION=reverb
  • REVERB_SERVER_HOST=0.0.0.0
  • REVERB_SERVER_PORT=8080

Para o cliente (browser), use o domínio com TLS do proxy (porta 443):

  • REVERB_HOST=ws.seu-dominio.com
  • REVERB_PORT=443
  • REVERB_SCHEME=https

E para o Vite:

  • VITE_REVERB_APP_KEY=${REVERB_APP_KEY}
  • VITE_REVERB_HOST=${REVERB_HOST}
  • VITE_REVERB_PORT=${REVERB_PORT}
  • VITE_REVERB_SCHEME=${REVERB_SCHEME}

5) Credenciais do Composer (Flux Pro)

Este projeto depende de livewire/flux-pro (repositório privado do Composer).

Pode autenticar o Flux Pro de três formas (o Dockerfile usa esta ordem):

  1. Build secret composer_auth — valor = JSON numa linha (o mesmo que usarias em COMPOSER_AUTH local).
  2. Build arg COMPOSER_AUTH_JSON — o mesmo JSON (http-basiccomposer.fluxui.dev). Não uses o nome COMPOSER_AUTH como build-arg: o Docker injeta o ARG vazio e o Composer pode falhar antes de aplicar o JSON real.
  3. Build args USERNAME + KEY — e-mail e license key; o Dockerfile corre composer config http-basic.composer.fluxui.dev … (cria auth.json na imagem).
  • username / USERNAME: e-mail da conta Flux.

  • password / KEY: a license key completa (UUID) — tem de ir inteira no build; se estiver truncada, o Composer devolve 401.

  • Se o Build Type estiver como Dockerfile, configure uma das opções acima (secret primeiro, depois COMPOSER_AUTH_JSON, depois USERNAME+KEY).

  • Environment ≠ Build: variáveis na secção Environment do Dokploy são para o contentor em runtime. O composer install corre no build da imagem — aí precisa de Build-time Arguments ou build secret. Colocar só USERNAME/KEY em Environment não alimenta o Docker build.

  • Segurança: build secret não entra no docker history da imagem; ARG pode ficar visível no histórico — prefira secret em produção. Evite expor a license key em logs de CI/deploy.

  • Se você estiver usando Docker Compose e rodando composer install pós-deploy, aí sim a credencial precisa existir como env var runtime (COMPOSER_AUTH).

Erro: bootstrap/cache must be present and writable

O .dockerignore não copia bootstrap/cache nem storage. O Dockerfile cria essas pastas antes do composer install para o script package:discover conseguir escrever ficheiros de cache.

Não versione auth.json no git e rotacione tokens expostos.

6) Comandos pós-deploy (somente Docker Compose)

Rode estes comandos após subir os containers (via “Deploy Commands / Post Deploy” do Dokploy):

  • composer install --no-dev --optimize-autoloader --no-interaction
  • npm ci && npm run build
  • php artisan migrate --force
  • php artisan storage:link || true
  • php artisan config:cache
  • php artisan route:cache || true
  • php artisan view:cache

9) Deploy via Dockerfile (recomendado)

9.1) Pré-requisitos

  • O repo precisa ter um Dockerfile na raiz (este projeto tem).
  • Configure a credencial do Flux Pro como Build-time Secret (recomendado) para não vazar no build log.
    • Secret name: composer_auth
    • Secret value: o JSON completo (em uma linha), igual ao COMPOSER_AUTH em CI/local

9.2) Configuração no Dokploy

  • Build Type: Dockerfile
  • Dockerfile Path: Dockerfile
  • Exposed Port: 80

9.3) Variáveis mínimas para parar o 500

  • APP_ENV=production
  • APP_DEBUG=false
  • APP_URL=https://seu-dominio.com (sem barra final; deve coincidir com o domínio público)
  • APP_KEY=base64:...

9.4) Avisos no log de build (Ubuntu / apt)

Mensagens como Failed to resolve user 'systemd-network' ou systemd-journal durante a instalação de pacotes são ruído habitual em imagens minimalistas e não indicam falha do deploy.

9.5) npm audit (vulnerabilidades no build)

O passo npm ci pode reportar vulnerabilidades; o build continua. Tratar em desenvolvimento com npm audit / npm audit fix (sempre com testes), não é bloqueio do Docker em si.

9.6) 404 em https://seu-dominio/ com build OK

O projeto define rota / (página inicial Livewire). Se o browser mostra 404 mas o contentor está saudável:

  1. Dentro do contentor (exec no Dokploy / SSH):
    curl -sI http://127.0.0.1/ e curl -sI http://127.0.0.1/up
    • 200 em ambos → Laravel e Nginx OK; o 404 vem do roteamento antes do app (domínio aponta para outro serviço, app errada no Dokploy, ou porta interna ≠ 80).
  2. No Dokploy: confirmar que o domínio (ex.: kolos.com.br) está associado a esta aplicação e que o target do proxy usa a porta 80 do serviço que corre esta imagem.
  3. Confirmar APP_URL igual ao URL público (HTTPS, sem path extra).
  4. Se só / falhar mas /up funcionar: limpar caches de rota no pós-deploy (php artisan route:clear, depois php artisan route:cache se usares cache de rotas).

9.7) APP_KEY aparece no printenv mas o log diz No application encryption key (/ 500, /up 200)

O PHP-FPM (por defeito) usa clear_env = yes: os workers não herdam as variáveis do contentor. O teu docker exec … bash vê o ambiente; o PHP através do Nginx não.

A imagem deste repo inclui docker/prod/php-fpm-pool-zz-kolos.conf/etc/php/8.5/fpm/pool.d/zz-kolos-env.conf com clear_env = no para o pool www.

  • Sem rebuild: podes confirmar com php -r "var_export(getenv('APP_KEY'));" (CLI costuma ver o env) vs. um script .php servido pelo FPM — o sintoma desaparece após redeploy com a imagem corrigida.
  • Workaround manual (não recomendado): listar cada variável em env[APP_KEY] = … no www.conf do FPM.

9.8) Mixed content: página em HTTPS mas CSS/JS bloqueados (http://…/build/…)

O browser carrega https:// mas os tags apontam para http:// quando o Laravel acha que o pedido é HTTP (ligação interna contentor ↔ proxy TLS).

  1. APP_URL tem de ser https://seu-dominio.com (nunca http:// em produção pública).
  2. O projeto confia em proxies (bootstrap/app.php) e, se APP_URL for https://, força esquema HTTPS nas URLs geradas (AppServiceProvider).
  3. A imagem repassa X-Forwarded-Proto ao PHP (docker/prod/nginx-default.conf). Após mudar Nginx, rebuild da imagem.
  4. Se alteraste env: php artisan config:clear (ou redeploy sem config:cache obsoleto com URL errada).

7) SSL

  • Configure seu-dominio.com no Dokploy e habilite Let’s Encrypt.
  • Configure ws.seu-dominio.com e habilite Let’s Encrypt.

8) Portas na VPS

  • Liberar: 80 e 443 (e 22 para SSH)