Por que "só dar git clone" não é suficiente
Se você tentar rodar o projeto logo depois de um git clone, ele não vai funcionar — e isso é esperado, não um erro seu. O motivo é o .gitignore: um arquivo que já vem configurado em todo projeto Laravel, listando pastas e arquivos que nunca devem ir pro controle de versão (Git). Repare no dele:
.gitignore (trecho relevante)
/vendor
/node_modules
.env
/public/storage
/storage/*.log
/storage/framework/cache/*
/storage/framework/sessions/*
/storage/framework/views/*
Cada linha tem um motivo: /vendor (as bibliotecas PHP instaladas pelo Composer) e /node_modules não precisam ir pro Git porque são reconstruíveis — dá pra recriar exatamente as mesmas versões a partir de dois arquivos que são versionados: composer.json/composer.lock. .env guarda senhas de banco, chaves de API, tokens — coisas que, se fossem parar num repositório Git (principalmente um público), estariam expostas pra qualquer um que visse o histórico. Os arquivos dentro de storage/ são gerados em tempo de execução (logs, cache, sessões) — não fazem sentido "congelados" no controle de versão.
Isso significa que, toda vez que o projeto for parar numa máquina nova (a sua, um colega, ou o servidor de produção), essas peças precisam ser reconstruídas manualmente. É exatamente esse processo que essa aula cobre, passo a passo.
Passo 1 — clonar e instalar as dependências PHP
terminal
git clone https://github.com/sua-conta/painel-usuarios.git
cd painel-usuarios
composer install --optimize-autoloader --no-dev
composer install lê o composer.lock (que trava as versões exatas de cada dependência, garantindo que todo mundo instale exatamente a mesma coisa) e recria a pasta vendor/ do zero. --no-dev pula pacotes que só servem pra desenvolvimento (ferramentas de debug, por exemplo) — use essa flag em produção; num ambiente de desenvolvimento novo, rode só composer install sem ela. --optimize-autoloader monta um mapa de classes mais eficiente, deixando o carregamento um pouco mais rápido — vale a pena em produção.
Painel de hospedagem tipo aaPanel? Pode precisar do caminho completo do PHPSe você instalar o projeto num servidor gerenciado por um painel — aaPanel, cPanel, Plesk — é comum ele manter várias versões de PHP instaladas lado a lado, sem deixar o comando
php do terminal apontando pra versão certa (às vezes nem aponta pra nenhuma). Rodar
php artisan migrate nesse cenário pode usar uma versão diferente da que seu projeto precisa, ou simplesmente devolver "command not found". A solução é chamar o binário do PHP pelo caminho completo, em vez de confiar no
php genérico. No aaPanel, o padrão é
/www/server/php/{versão}/bin/php — o número da versão sem o ponto (
83 = PHP 8.3,
82 = PHP 8.2). Então, em vez de
php artisan migrate, você roda:
terminal (aaPanel ou similar)
/www/server/php/83/bin/php artisan migrate
Isso vale pra
qualquer comando
php artisan mostrado daqui em diante nessa aula — troque
php pelo caminho completo sempre que estiver num painel assim. Pra descobrir o caminho certo no seu, veja a seção de configuração de PHP dele (no aaPanel, "Software" → "PHP" já mostra onde cada versão fica instalada), ou rode
which php no terminal do servidor pra conferir se já existe algum atalho configurado.
"detected dubious ownership in repository" — outro clássico de painelSe ao rodar um comando
git no servidor (um
git pull, por exemplo) aparecer um erro citando
dubious ownership, é uma proteção do próprio Git: ele recusa operar numa pasta cujo dono não é o mesmo usuário rodando o comando. É comum em painéis, onde os arquivos do projeto pertencem ao usuário do servidor web (
www-data, ou um usuário específico do painel) mas você está rodando
git como root ou outro usuário via SSH. A solução é marcar a pasta como seguros explicitamente, uma única vez por servidor:
terminal (no servidor)
git config --global --add safe.directory /www/wwwroot/mapa.beepsttore.com
Troque o caminho pelo caminho real do seu projeto — depois disso, o Git para de reclamar dessa pasta especificamente.
Passo 2 — recriando o .env
Como o .env não veio no clone (está no .gitignore), você precisa criar o seu próprio, a partir de um arquivo de exemplo que esse sim é versionado — o .env.example, que lista todas as variáveis que existem, sem os valores reais/secretos:
terminal
cp .env.example .env
Agora edite o .env recém-criado com os valores reais desse ambiente: credenciais do banco de dados (DB_DATABASE, DB_USERNAME, DB_PASSWORD), APP_URL, as chaves da evolution-api (Aula 13), configuração de e-mail (Aula 22) — tudo que você já viu na Aula 21 sobre config personalizada.
APP_ENV e APP_DEBUG — atenção redobrada em produçãoDuas variáveis do .env merecem cuidado especial: APP_ENV=production (identifica o ambiente) e, principalmente, APP_DEBUG=false. Com APP_DEBUG=true, qualquer erro no sistema mostra pro visitante uma página cheia de detalhes internos — caminho de arquivos do servidor, trechos de código, variáveis de ambiente, às vezes até credenciais de banco. Isso é ótimo pra você depurando localmente, e um risco de segurança real se ficar ligado em produção. Nunca esqueça de conferir essa linha antes de publicar.
Passo 3 — gerando a chave da aplicação
terminal
php artisan key:generate
APP_KEY é usada pelo Laravel pra criptografar dados sensíveis — sessões de usuário, cookies, e qualquer coisa que você criptografar manualmente via Crypt::encrypt(). Cada ambiente (seu computador, o servidor de produção) deve ter a sua própria chave, gerada nesse passo — nunca copie a chave de um ambiente pro outro, e nunca a exponha num repositório público.
Um detalhe importante: rode esse comando só na primeira vez que o ambiente é configurado. Se você rodar de novo depois que o sistema já estiver em uso, com sessões ativas ou dados criptografados salvos com a chave antiga, esses dados ficam ilegíveis — a chave nova não consegue descriptografar o que foi criptografado com a antiga.
Passo 4 — banco de dados
terminal
php artisan migrate --force
Normalmente, quando o Laravel detecta APP_ENV=production, o comando migrate pergunta uma confirmação antes de rodar (é uma operação que pode alterar dados de verdade, então o framework é cauteloso por padrão). A flag --force pula essa pergunta — necessária em scripts de deploy automatizados, que não têm ninguém ali pra digitar "sim".
Se for a primeira vez que esse banco existe (produção nova, do zero), você também quer o AdminSeeder da Aula 20 rodando junto:
terminal (só na primeira vez)
php artisan migrate --force --seed
Passo 5 — o link de storage e as permissões de arquivo
terminal
php artisan storage:link
Esse é o mesmo comando da Aula 25 — como ele cria um link simbólico (um "atalho" no sistema de arquivos), e links simbólicos não são replicados pelo Git, precisa ser refeito em cada ambiente novo.
Em servidores Linux (o caso mais comum com Apache), o usuário que roda o servidor web (geralmente chamado www-data em distribuições baseadas em Debian/Ubuntu) precisa ter permissão de escrita em duas pastas específicas — sem isso, você vai ver erros de "permission denied" ao tentar gravar logs, cache, ou arquivos enviados por upload:
terminal (no servidor)
chmod -R 775 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache
Passo 6 — cache de otimização (só em produção)
Antes de gerar cache novo, vale limpar qualquer cache antigo que possa ter sobrado de um deploy anterior — evita ficar "cache em cima de cache" com valores desatualizados:
terminal
php artisan optimize:clear
optimize:clear limpa de uma vez todos os caches que o Laravel mantém — config, rotas, views, e mais alguns que ainda não vimos. É o oposto do que vamos rodar a seguir, e um bom primeiro passo sempre que algo "não está atualizando" depois de um deploy, mesmo fora desse checklist.
Esses três comandos "congelam" partes do sistema num formato mais rápido de carregar — vale a pena em produção, onde performance importa; em desenvolvimento local, geralmente é melhor não usar, porque eles atrapalham a atualização automática quando você edita um arquivo:
terminal
php artisan config:cache
php artisan route:cache
php artisan view:cache
config:cache junta todos os arquivos de config/ (incluindo os valores já lidos do .env, lembra da Aula 21?) num único arquivo compilado. route:cache faz o mesmo com as rotas. view:cache pré-compila os arquivos Blade pra HTML/PHP puro, evitando recompilar a cada requisição.
A pegadinha do config:cacheDepois de rodar config:cache, o Laravel para de ler o .env diretamente — ele passa a usar só o que foi congelado no cache. Se você editar o .env depois disso (pra trocar uma senha, por exemplo) e a mudança não parecer ter efeito nenhum, é isso: rode php artisan config:clear (ou config:cache de novo) pra recriar o cache com os valores atualizados.
Como o Tailwind desse projeto vem via CDN (Aula 28, um <script> direto no layout), não existe nenhum passo de build de JavaScript aqui — o projeto é 100% PHP/Blade servido direto pelo Apache, sem npm run build nenhum.
Passo 7 — mantendo os Listeners em fila de pé (Supervisor)
Falta uma peça que os passos anteriores não cobrem: os Listeners com ShouldQueue (Aula 15) — os que mandam e-mail e WhatsApp de boas-vindas — não rodam sozinhos quando algo cai na fila. Eles ficam esperando até um processo php artisan queue:work vir processá-los. Rodar esse comando manualmente uma vez não é suficiente pra produção: se ele cair (reinício do servidor, erro de memória, deploy novo), ninguém volta a processar a fila até alguém perceber e religar na mão.
É pra isso que serve o Supervisor: um gerenciador de processos do Linux que mantém o queue:work de pé, reiniciando ele sozinho sempre que cair. Repare que isso não é a mesma coisa que o crontab da Aula 14 — o crontab aciona o agendador (schedule:run, uma vez por minuto); o Supervisor mantém o worker da fila rodando continuamente. São duas peças diferentes, cuidando de duas responsabilidades diferentes — a maioria dos projetos com fila usa as duas juntas, não uma no lugar da outra.
terminal (no servidor)
sudo apt install supervisor
/etc/supervisor/conf.d/painel-usuarios-worker.conf
[program:painel-usuarios-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /caminho/do/projeto/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
numprocs=1
user=www-data
stdout_logfile=/caminho/do/projeto/storage/logs/worker.log
command é o processo que o Supervisor mantém vivo — o mesmo queue:work que você rodaria manualmente, só que agora vigiado. autorestart=true é o coração disso tudo: se o processo morrer por qualquer motivo, o Supervisor sobe outro na hora. --tries=3 é do próprio Laravel, não do Supervisor — quantas vezes tentar reprocessar um job que falhou antes de desistir dele. numprocs=1 controla quantas cópias rodam em paralelo; mais de uma processa a fila mais rápido, à custa de mais memória — pra um projeto desse tamanho, uma já basta.
terminal
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start painel-usuarios-worker:*
reread + update fazem o Supervisor descobrir essa configuração nova; start liga o processo pela primeira vez. Dali em diante, ele fica de pé sozinho, sobrevivendo a queda de processo e (se o Supervisor estiver configurado pra iniciar no boot, o padrão na maioria das instalações) até a reinícios do servidor inteiro.
Trocou o código do Listener? Reinicie o workerO queue:work carrega o código da aplicação uma única vez, quando inicia — se você fizer deploy de uma mudança nos Listeners, o worker antigo continua rodando a versão velha até ser reiniciado. Rode php artisan queue:restart depois de cada deploy (o Supervisor sobe um processo novo automaticamente, já com o código atualizado) — vale adicionar esse comando no fim do seu checklist de atualização, na tabela abaixo.
Quando o git pull reclama de mudanças locais
Às vezes um arquivo que é versionado (não está no .gitignore) acaba sendo modificado direto no servidor — de propósito (um ajuste específico daquele ambiente) ou sem querer. Quando isso acontece, git pull recusa continuar, com medo de sobrescrever uma mudança que ninguém commitou. Duas saídas, dependendo do que você quer fazer:
Descartar tudo que foi modificado localmente, sem exceção:
terminal
git reset --hard HEAD
git pull origin main
reset --hard HEAD volta cada arquivo rastreado exatamente pro estado do último commit, jogando fora qualquer mudança local — é destrutivo e sem confirmação, então só use tendo certeza de que não tem nada ali que precise guardar.
Ignorar mudanças só de um arquivo específico, mantendo o resto normal:
Isso é diferente de colocar o arquivo no .gitignore (que só funciona pra arquivos que o Git ainda não rastreia). Pra um arquivo que já está no repositório, mas que você quer que o Git pare de notar mudanças locais nele:
terminal
git update-index --skip-worktree caminho/do/arquivo
Dali em diante, o Git ignora qualquer modificação local nesse arquivo específico em operações como pull/status/checkout — útil pra um arquivo de configuração com um ajuste só daquele servidor, sem precisar mexer no .gitignore nem arriscar perder esse ajuste no próximo pull. Pra reverter e voltar a rastrear normalmente: git update-index --no-skip-worktree caminho/do/arquivo.
Passo 8 — garantindo HTTPS nos links gerados
Dependendo de como o servidor/painel está configurado, o Laravel às vezes não detecta sozinho que a conexão é HTTPS — e aí passa a gerar links e URLs de asset como http:// mesmo numa página carregada com certificado válido, o que o navegador trata como "conteúdo misto" e pode bloquear. A forma mais direta de evitar isso é forçar o esquema HTTPS explicitamente em produção:
app/Providers/AppServiceProvider.php (adicionando ao boot() que já existe)
use Illuminate\Support\Facades\URL;
public function boot(): void
{
// Enforce HTTPS in production environments
if (app()->isProduction()) {
URL::forceHttps();
}
// ...resto do boot() (Gates, etc.)
}
app()->isProduction() é um atalho pra app()->environment('production') — confere o APP_ENV do .env. URL::forceHttps() faz o Laravel gerar toda URL (rotas, asset(), os links do @vite se você estiver na trilha de Vue) sempre com https://, independente do que ele tenha detectado na requisição — sem precisar mexer em configuração de proxy nenhuma. Restringir ao ambiente de produção evita que isso atrapalhe o php artisan serve local, que normalmente roda em HTTP mesmo. Não esqueça também de conferir o APP_URL no .env de produção, já com o esquema certo: APP_URL=https://seu-dominio.com.
Resumo: primeiro deploy vs. atualização do dia a dia
O checklist inteiro acima só é necessário na primeira vez que o projeto chega num ambiente novo. Pra publicar uma atualização num servidor que já está rodando, o processo é bem mais curto:
| Primeiro deploy (ambiente novo) | Atualização (servidor já rodando) |
git clone | git pull origin main |
composer install | composer install (só se o composer.lock mudou) |
cp .env.example .env + editar | não repete — o .env já existe e fica intacto |
php artisan key:generate | não repete — geraria uma chave nova e quebraria dados já criptografados |
migrate --force --seed | migrate --force (sem --seed, pra não duplicar o admin) |
storage:link + permissões | não repete, a menos que a pasta storage/ tenha sido recriada |
| Configurar e iniciar o Supervisor | não repete — já fica de pé sozinho |
Adicionar o URL::forceHttps() no AppServiceProvider | não repete — fica no código, versionado |
optimize:clear + config:cache / route:cache / view:cache | rodar de novo sempre, pra pegar o código atualizado |
| — | php artisan queue:restart, pra o worker pegar o código novo |
Mão na massa
Simule um "primeiro deploy" na sua própria máquina: faça uma cópia de segurança do seu .env atual (renomeie pra .env.backup, por exemplo), depois apague as pastas vendor/ e o arquivo .env. Siga o checklist inteiro, do Passo 1 ao Passo 6 (pule os comandos de cache, que atrapalham o desenvolvimento local), usando os mesmos dados de banco que você já tinha. No final, confirme que o sistema volta a funcionar exatamente como antes — login, cadastro de usuário, tudo. Se tiver acesso a um servidor Linux de teste, configure o Supervisor do Passo 7 de verdade: cadastre um usuário como gerente, mate o processo do queue:work na mão (kill no PID dele) e confirme que o Supervisor sobe outro sozinho em poucos segundos.