Servidor de deploy (Laravel)
ver o scriptProvisiona o servidor, cria a estrutura do projeto e gera o script de deploy zero-downtime.
bash <(curl -s https://raw.githubusercontent.com/edsuuu/Linux-Devlopment/main/scripts/deploy/setup.sh)
Ao iniciar, o script pergunta o modo — navegue com as setas ↑ ↓ e confirme com Enter:
1) Instalação completa
Instala o servidor (tabela abaixo) e em seguida cria o projeto. Use num servidor novo.
2) Apenas o projeto
Pula a instalação e só cria a estrutura e o script de deploy. Use quando o servidor já está pronto.
O que a instalação completa instala
| Item | Detalhes |
|---|---|
| PHP | Versão disponível no apt do sistema, com as extensões cli fpm mbstring xml curl mysql sqlite3 zip gd bcmath intl |
| Composer | Instalador oficial, em /usr/local/bin/composer |
| Node.js | Via nodesource — menu com 26, 24 (padrão), 22 e 20 |
| nginx | Instalado, habilitado no boot e configurado pro projeto se você informar o domínio |
| pnpm | Habilitado via corepack (vem com o Node) |
| apache2 | Desativado e removido se aparecer como dependência do PHP — aqui o site é servido por nginx |
Idempotente: o que já existe é pulado, então rodar de novo não quebra nada.
Estrutura de pastas no servidor
Como o projeto fica em /var/www/projects depois do setup e de alguns deploys:
meuprojeto
├── current -> releases/2026-08-04-130511
├── releases
│ ├── 2026-08-04-101503
│ ├── 2026-08-04-121022
│ └── 2026-08-04-130511
│ ├── .env
│ ├── storage -> /var/www/projects/meuprojeto/shared/storage
│ └── public
│ └── storage -> /var/www/projects/meuprojeto/shared/storage/app/public
└── shared
├── .env
└── storage
├── app
│ └── public
├── framework
│ ├── cache
│ ├── sessions
│ ├── testing
│ └── views
└── logs
current— symlink pra release ativa. Aponte orootdo nginx pracurrent/public.releases— uma pasta por deploy, nomeada pela data. Só as 3 mais novas ficam.shared— o que sobrevive entre releases: o.enve ostorage.
O .env é copiado, não é symlink
Repare na árvore: o storage tem seta (é symlink pro shared/), o .env não — cada release recebe uma cópia própria do shared/.env, feita no momento do deploy.
Na prática: editar o shared/.env não muda a release que está no ar — o valor novo só entra no próximo deploy. Pra valer agora, edite também o current/.env e rode artisan optimize.
nginx do projeto
O setup pergunta o domínio: informando um, ele grava o arquivo abaixo em /etc/nginx/sites-available/<projeto>, habilita o site, testa com nginx -t e recarrega. Deixando vazio, use este modelo à mão:
server {
listen 80;
listen [::]:80;
server_name seudominio.com.br;
root /var/www/projects/meuprojeto/current/public;
index index.php;
charset utf-8;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
client_max_body_size 32m;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
O $realpath_root no lugar do $document_root é o que faz o PHP receber o caminho real da release (releases/2026-…) em vez do caminho pelo symlink — sem isso o OPcache mistura releases.
nginx com HTTPS
Emita o certificado primeiro (veja Certificado SSL) — sem os arquivos .pem no lugar, o nginx -t falha. Com o certificado emitido, troque o conteúdo do arquivo do projeto por este: o primeiro server manda todo HTTP daquele domínio pro HTTPS, o segundo serve o site.
server {
listen 80;
listen [::]:80;
server_name seudominio.com.br;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name seudominio.com.br;
ssl_certificate /etc/letsencrypt/live/seudominio.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/seudominio.com.br/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/seudominio.com.br/chain.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
root /var/www/projects/meuprojeto/current/public;
index index.php;
charset utf-8;
client_max_body_size 32m;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Valide e recarregue:
sudo nginx -t
sudo systemctl reload nginx
Confira o socket do fastcgi_pass e o domínio nos três lugares antes de recarregar. Config genérica de HTTPS, fora da estrutura de deploy: Nginx HTTPS.
Se o servidor tiver mais de uma versão de PHP
Versões convivem sem problema — o que quebra é o CLI e o FPM apontarem para versões diferentes. O artisan e o composer rodam pelo PHP da linha de comando; o site roda pelo socket do FPM no fastcgi_pass. Se o CLI for 8.5 e só o FPM 8.3 estiver no ar, apontar pro socket 8.5 dá 502.
O setup checa isso: usa o socket da versão do CLI se ele existir, senão cai no socket do FPM que está rodando e avisa da diferença. O deploy faz o mesmo no reload. Pra conferir à mão: php -v e ls /run/php/.
O setup grava o script do projeto em scripts/, a partir do diretório onde você o rodou. A cada release:
./scripts/deploy-<projeto>.sh
O que o deploy faz
- 01Clone raso do branch numa release datada em
releases/ - 02No primeiro deploy, cria o
shared/.enva partir do.env.examplee roda okey:generate - 03Copia o
shared/.envpra dentro da release — o .env nunca é symlink — e liga ostoragepor symlink proshared/ - 04
composer install --no-deve assets pelo lockfile (pnpm ou npm ci) - 05
migrate --force+artisan optimize - 06Troca atômica do symlink
current, mantém as 3 releases mais novas - 07Recarrega o
php-fpmpra limpar o OPcache e o realpath cache
Por que recarregar o php-fpm
O PHP guarda em cache o bytecode (OPcache) e o caminho real de cada arquivo (realpath cache, 120s por padrão). Como o current é um symlink, sem recarregar o FPM o site continua servindo o código da release anterior por alguns minutos depois do deploy.
O deploy roda systemctl reload php<versão>-fpm logo após trocar o symlink, o que reinicia os workers e zera os dois caches sem derrubar requisição.
pnpm
Fixe "packageManager": "pnpm@10.12.1" no package.json — sem isso o corepack baixa o pnpm mais novo, que pode exigir Node mais novo que o do servidor.