Preparació per a producció
Optimitzacions i passos necessaris per desplegar Laravel a producció: cache, assets, permisos, variables d'entorn i checklist completa.
Preparar una aplicació Laravel per a producció és molt més que canviar APP_ENV a production i pujar el codi. És un procés sistemàtic que cobreix configuració, optimització, seguretat i infraestructura. Cada pas que t'estalvies durant la preparació és un problema potencial que apareixerà en el pitjor moment: quan l'aplicació ja està servint usuaris reals. Un error de configuració pot exposar dades sensibles, una falta d'optimització pot fer que l'aplicació sigui lenta sota càrrega, i uns permisos incorrectes poden obrir forats de seguretat.
Aquesta guia recorre tots els passos necessaris, des de la configuració de les variables d'entorn fins a la verificació final del desplegament. Segueix-la com un checklist i adapta-la a les necessitats específiques del teu projecte.
Configuració de l'entorn#
El fitxer .env és el cor de la configuració de Laravel. En producció, cada variable ha d'estar configurada amb cura perquè un valor incorrecte pot tenir conseqüències greus. El fitxer .env mai s'ha de cometre al repositori Git (ja està al .gitignore per defecte), i cada entorn (local, staging, producció) ha de tenir el seu propi fitxer amb els valors apropiats.
Variables essencials#
Les variables més crítiques per a producció són les que controlen el comportament general de l'aplicació:
APP_NAME="La Meva Aplicació"
APP_ENV=production
APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
APP_DEBUG=false
APP_URL=https://lamevaaplicacio.comAPP_DEBUG=false és absolutament crític en producció. Amb APP_DEBUG=true, Laravel mostra traces d'error completes que inclouen variables d'entorn, credencials de base de dades, claus d'API i tot el contingut del fitxer .env. Un atacant que provoqui un error (per exemple, accedint a una URL inexistent amb un paràmetre malformat) obtindria accés complet a totes les credencials de l'aplicació. Mai, sota cap circumstància, activis el mode debug en producció.
La variable APP_KEY és la clau de xifrat que Laravel utilitza per encriptar cookies, sessions, tokens CSRF i qualsevol dada que passi per la funció encrypt(). Ha de ser una cadena de 32 bytes codificada en base64, generada amb php artisan key:generate. Si canvies aquesta clau en una aplicació en funcionament, totes les sessions actives s'invalidaran i les dades encriptades anteriorment seran il·legibles. Genera-la un cop i no la canviïs mai a menys que hi hagi una raó de seguretat per fer-ho.
APP_URL ha d'incloure el protocol (https://) i el domini exacte sense barra final. Laravel utilitza aquesta variable per generar URLs absolutes amb url() i route(), per als enllaços dels correus electrònics i per a la generació del sitemap. Un valor incorrecte pot provocar enllaços trencats a tota l'aplicació.
Base de dades, cache, sessions i cues#
En producció, els drivers de cache, sessions i cues han de ser serveis dedicats, no els drivers de fitxer que venen per defecte. Redis és la opció recomanada per a tots tres perquè és extremadament ràpid, suporta operacions atòmiques i pot gestionar milers de connexions simultànies:
# Base de dades
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=la_meva_app
DB_USERNAME=app_user
DB_PASSWORD=contrasenya-segura-i-llarga
# Cache, sessions i cues amb Redis
CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
# Connexió Redis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379Si no pots utilitzar Redis, el driver database és una alternativa acceptable per a sessions i cues, però el rendiment serà inferior. El driver file per a la cache és acceptable per a aplicacions petites amb poc tràfic, però sota càrrega, les operacions de fitxer es converteixen en un coll d'ampolla.
Per a la base de dades, utilitza sempre un usuari específic per a l'aplicació amb els permisos mínims necessaris. No utilitzis mai l'usuari root en producció. Crea un usuari que tingui permisos de lectura, escriptura i modificació d'estructura (per a les migracions) només sobre la base de dades de l'aplicació.
Configuració de logs#
En producció, no vols que els logs s'acumulin en un sol fitxer gegant que acabi ocupant tot l'espai del disc. El driver daily rota els fitxers automàticament creant un fitxer per dia i eliminant els antics. El nivell warning filtra els missatges menys importants (debug, info) perquè en producció no necessites registrar cada consulta SQL o cada petició HTTP:
LOG_CHANNEL=stack
LOG_STACK=daily,sentry
LOG_LEVEL=warning
LOG_DEPRECATIONS_CHANNEL=nullEl canal stack permet combinar múltiples canals de log. En l'exemple, els logs es guarden en fitxers diaris localment i també s'envien a Sentry (o un servei similar) per a monitorització centralitzada. Sentry agrupa els errors per tipus, mostra la freqüència i envia alertes quan apareixen errors nous, cosa que el fa indispensable per a aplicacions en producció.
// config/logging.php
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily', 'sentry'],
'ignore_exceptions' => false,
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'warning'),
'days' => 14, // Mantenir logs dels últims 14 dies
'replace_placeholders' => true,
],
'sentry' => [
'driver' => 'sentry',
'level' => env('LOG_LEVEL', 'warning'),
'bubble' => true,
],
],Comandes d'optimització#
Laravel ofereix un conjunt de comandes que preparen l'aplicació per funcionar de la manera més eficient possible. Cadascuna d'elles pre-processa un aspecte diferent de l'aplicació perquè en producció no hagi de fer-ho en cada petició.
Cache de configuració#
La comanda config:cache llegeix tots els fitxers de configuració del directori config/, els fusiona en un sol fitxer PHP i el guarda a bootstrap/cache/config.php. En lloc de llegir i parsejar desenes de fitxers de configuració en cada petició, Laravel carrega un sol fitxer. L'impacte en el rendiment és significatiu, especialment en aplicacions amb molts paquets que afegeixen els seus propis fitxers de configuració:
php artisan config:cacheQuan la configuració està cacheada, la funció env() només funciona dins dels fitxers de configuració (config/*.php). Si crides env() directament des d'un controlador, middleware, model o qualsevol altre lloc, retornarà null. Això és perquè un cop la configuració està cacheada, Laravel no carrega el fitxer .env en absolut. Assegura't que totes les crides a env() estan dins dels fitxers de configuració i que la resta del codi utilitza config() per accedir als valors.
Per exemple, en lloc de fer env('STRIPE_KEY') dins d'un servei, defineix el valor al fitxer de configuració:
// config/services.php
'stripe' => [
'key' => env('STRIPE_KEY'), // env() aquí SÍ funciona
'secret' => env('STRIPE_SECRET'), // perquè és un fitxer config/
],// app/Services/PaymentService.php
class PaymentService
{
public function __construct()
{
// Correcte: utilitza config()
$key = config('services.stripe.key');
// INCORRECTE: retornarà null amb config:cache
// $key = env('STRIPE_KEY');
}
}Cache de rutes#
La comanda route:cache serialitza totes les definicions de rutes en un format compilat que Laravel pot carregar molt més ràpidament que parsejar els fitxers de rutes. Això és especialment notable en aplicacions grans amb centenars de rutes, on la millora de rendiment pot ser de diversos mil·lisegons per petició:
php artisan route:cacheLa cache de rutes funciona amb totes les rutes basades en controladors. Si tens rutes que utilitzen closures directament (funcions anònimes), la cache de rutes fallarà perquè PHP no pot serialitzar closures. En producció, totes les rutes han d'apuntar a mètodes de controladors:
// Funciona amb route:cache
Route::get('/usuaris', [UserController::class, 'index']);
// NO funciona amb route:cache (closure)
Route::get('/usuaris', function () {
return User::all();
});Cache de vistes#
La comanda view:cache pre-compila totes les plantilles Blade a PHP. Normalment, Laravel compila cada plantilla Blade la primera vegada que es renderitza i la guarda en cache. Amb view:cache, aquesta compilació es fa per endavant per a totes les vistes, eliminant el petit retard de la primera renderització:
php artisan view:cacheCache d'events#
La comanda event:cache cacheja la descoberta automàtica d'events i listeners. Si l'aplicació utilitza la descoberta automàtica d'events (que és el comportament per defecte a Laravel), el framework escaneja els directoris de listeners en cada petició per trobar les associacions entre events i listeners. La cache elimina aquest escaneig:
php artisan event:cacheLa comanda optimize#
La comanda optimize executa totes les caches anteriors d'un sol cop. És la manera més pràctica d'optimitzar l'aplicació durant el desplegament:
# Equival a executar config:cache, route:cache, view:cache i event:cache
php artisan optimizePer netejar totes les caches (útil durant el desenvolupament o quan necessites forçar una recàrrega completa):
# Neteja totes les caches d'optimització
php artisan optimize:clearOptimització de Composer#
Composer té les seves pròpies optimitzacions que són importants en producció. L'opció --no-dev elimina totes les dependències de desenvolupament (PHPUnit, Faker, Telescope, etc.), reduint l'espai en disc i la superfície d'atac. L'opció --optimize-autoloader genera un classmap optimitzat que permet a PHP trobar les classes directament en lloc de buscar-les per directoris:
composer install --no-dev --optimize-autoloaderPer a una optimització encara més agressiva, pots utilitzar el classmap autoritari, que indica a Composer que NOMÉS busqui classes al classmap generat, sense fer fallback a la càrrega per directoris. Això és lleugerament més ràpid, però requereix que totes les classes estiguin correctament registrades:
composer install --no-dev --optimize-autoloader --classmap-authoritativeNo executis composer install amb --no-dev al teu entorn local. Eliminaria les dependències de desenvolupament com PHPUnit, Laravel Dusk o Telescope. L'opció --no-dev és exclusivament per a entorns de producció i staging.
Compilació d'assets#
Si l'aplicació utilitza Vite (el bundler d'assets per defecte de Laravel), necessites compilar els assets CSS i JavaScript per a producció. La compilació de producció minifica el codi, elimina el codi mort (tree-shaking), optimitza les imatges i genera noms de fitxer amb hash per al cache busting:
npm run buildEl cache busting és el mecanisme que garanteix que els navegadors carreguin la versió correcta dels assets. Vite genera noms de fitxer amb un hash únic basat en el contingut (per exemple, app-Bf3a2c1e.js). Quan el contingut canvia, el hash canvia i el navegador descarrega la nova versió en lloc d'utilitzar la versió cacheada. Laravel gestiona això automàticament amb la directiva @vite:
{{-- Al teu layout Blade --}}
@vite(['resources/css/app.css', 'resources/js/app.js'])
{{-- En producció, genera algo com: --}}
<link rel="stylesheet" href="/build/assets/app-Bf3a2c1e.css">
<script type="module" src="/build/assets/app-Da4b5f2e.js"></script>El directori public/build que genera Vite sí que s'ha de cometre al repositori o generar-se durant el desplegament. La majoria d'equips opten per generar-lo durant el desplegament per mantenir el repositori net.
Permisos de fitxers#
Laravel necessita permís d'escriptura en dos directoris: storage/ (per a logs, cache de fitxers, sessions de fitxer i fitxers pujats) i bootstrap/cache/ (per a les caches d'optimització). La resta de l'aplicació hauria de ser de només lectura per al servidor web.
# Donar permisos d'escriptura al servidor web
sudo chown -R www-data:www-data storage bootstrap/cache
sudo chmod -R 775 storage bootstrap/cacheEl permís 775 significa que el propietari i el grup poden llegir, escriure i executar, mentre que la resta d'usuaris només poden llegir i executar. Això és adequat quan l'usuari del servidor web (normalment www-data a Ubuntu/Debian) és el propietari o pertany al grup dels directoris.
No utilitzis mai 777 (lectura, escriptura i execució per a tothom) en producció. El permís 777 significa que qualsevol usuari del sistema pot modificar i executar fitxers en aquests directoris, cosa que és un risc de seguretat greu. Si necessites depurar problemes de permisos, comprova quin usuari executa el procés PHP i assegura't que pertany al grup correcte:
# Comprovar quin usuari executa PHP-FPM
ps aux | grep php-fpm
# Comprovar els permisos actuals
ls -la storage/
ls -la bootstrap/cache/
# Si el desplegament es fa amb un usuari diferent del servidor web,
# afegeix l'usuari de desplegament al grup www-data
sudo usermod -aG www-data deploy-userUna alternativa més elegant és utilitzar ACLs (Access Control Lists), que permeten definir permisos per a múltiples usuaris sense canviar el propietari ni el grup:
# Donar permisos a www-data i a l'usuari de desplegament
sudo setfacl -Rm u:www-data:rwX,u:deploy:rwX storage bootstrap/cache
sudo setfacl -dRm u:www-data:rwX,u:deploy:rwX storage bootstrap/cacheConfiguració de PHP per a producció#
La configuració de PHP (php.ini) té un impacte directe en el rendiment i la seguretat de l'aplicació. A continuació, les configuracions més importants per a producció:
OPcache#
OPcache és una extensió de PHP que emmagatzema en memòria el bytecode compilat dels fitxers PHP. Sense OPcache, PHP ha de llegir, parsejar i compilar cada fitxer PHP en cada petició. Amb OPcache, el bytecode compilat es reutilitza directament des de la memòria, cosa que redueix dràsticament el temps d'execució:
; php.ini - Configuració OPcache per a producció
; Activar OPcache
opcache.enable=1
; Memòria assignada per al cache (en MB)
; 256 MB és adequat per a la majoria d'aplicacions Laravel
opcache.memory_consumption=256
; Nombre màxim de fitxers a cachear
; Una aplicació Laravel amb dependències pot tenir 10.000+ fitxers
opcache.max_accelerated_files=20000
; No comprovar si els fitxers han canviat (en producció no canvien entre desplegaments)
; Això elimina la crida stat() per cada fitxer en cada petició
opcache.validate_timestamps=0
; Guardar els comentaris (necessari per a anotacions i reflexió)
opcache.save_comments=1
; Prefetch del framework al iniciar
opcache.preload=/var/www/app/vendor/laravel/framework/src/preload.php
opcache.preload_user=www-data
; Percentatge de memòria malgastada abans de reiniciar (per fragmentació)
opcache.max_wasted_percentage=10Amb opcache.validate_timestamps=0, OPcache no detectarà canvis als fitxers PHP. Després de cada desplegament, has de reiniciar PHP-FPM per forçar OPcache a recarregar els fitxers: sudo systemctl reload php8.3-fpm. Si no ho fas, l'aplicació seguirà executant el codi antic.
Altres configuracions de PHP#
; Memòria màxima per procés (128-256 MB és adequat per a la majoria d'aplicacions)
memory_limit=256M
; Mida màxima de pujada de fitxers
upload_max_filesize=64M
post_max_size=64M
; Temps màxim d'execució d'un script (en segons)
; 30 segons és suficient per a peticions HTTP normals
max_execution_time=30
; Temps màxim d'entrada (parsing de dades POST)
max_input_time=60
; Desactivar funcions perilloses
disable_functions=exec,passthru,shell_exec,system,proc_open,popen
; No mostrar la versió de PHP a les capçaleres HTTP
expose_php=Off
; Zona horària
date.timezone=Europe/Andorra
; Codificació per defecte
default_charset=UTF-8
; Mida del buffer de realpath (millora el rendiment d'include/require)
realpath_cache_size=4096K
realpath_cache_ttl=600Desactivar funcions com exec i shell_exec amb disable_functions pot causar problemes si l'aplicació o algun paquet les necessita. Per exemple, alguns paquets de generació de PDF (com Browsershot) o de processament d'imatges (com Spatie Media Library amb conversions) necessiten proc_open. Comprova les dependències de l'aplicació abans de desactivar funcions.
Configuració de Nginx#
Nginx és el servidor web recomanat per a aplicacions Laravel en producció. La configuració següent inclou les optimitzacions essencials: compressió gzip, cache d'assets estàtics, capçaleres de seguretat i la redirecció correcta per al router de Laravel:
server {
listen 80;
server_name lamevaaplicacio.com www.lamevaaplicacio.com;
# Redirigir tot el tràfic HTTP a HTTPS
return 301 https://lamevaaplicacio.com$request_uri;
}
server {
listen 443 ssl http2;
server_name lamevaaplicacio.com;
# Redirigir www a no-www
if ($host = www.lamevaaplicacio.com) {
return 301 https://lamevaaplicacio.com$request_uri;
}
# Directori arrel
root /var/www/app/public;
index index.php;
# SSL (configurat per Certbot o Forge)
ssl_certificate /etc/letsencrypt/live/lamevaaplicacio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lamevaaplicacio.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Capçaleres de seguretat
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Mida màxima de pujada (ha de coincidir amb upload_max_filesize de PHP)
client_max_body_size 64M;
# Compressió gzip
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
application/javascript
application/json
application/xml
application/rss+xml
text/css
text/javascript
text/plain
text/xml
image/svg+xml;
# Cache d'assets estàtics
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Laravel: provar fitxer estàtic primer, després enviar a index.php
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP-FPM
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
# Timeouts
fastcgi_connect_timeout 60;
fastcgi_send_timeout 60;
fastcgi_read_timeout 60;
}
# Bloquejar accés a fitxers sensibles
location ~ /\.(?!well-known) {
deny all;
}
location ~ /\.env {
deny all;
}
# Desactivar logs per a favicon i robots
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
# Pàgina d'error personalitzada
error_page 404 /index.php;
}Els punts clau d'aquesta configuració son:
- Redirecció HTTP a HTTPS: Tot el tràfic no xifrat es redirigeix a HTTPS amb un codi 301 (redirecció permanent).
- Compressió gzip: Redueix significativament la mida de les respostes HTML, CSS, JavaScript i JSON. Un nivell de compressió de 5 ofereix un bon equilibri entre compressió i ús de CPU.
- Cache d'assets estàtics: Els fitxers CSS, JS, imatges i fonts es cacheen durant 1 any al navegador. La capçalera
immutableindica que el contingut no canviarà mai per a la mateixa URL (gràcies al cache busting de Vite). - Capçaleres de seguretat:
X-Frame-Optionsevita que la pàgina s'incrusti en un iframe (protecció contra clickjacking),X-Content-Type-Optionsevita que el navegador interpreti fitxers amb un MIME type diferent del declarat, iHSTSforça l'ús d'HTTPS durant un any. - Bloqueig de fitxers sensibles: Les ubicacions que comencen amb
.(com.env,.git) es bloquegen excepte.well-known(necessari per a Let's Encrypt).
L'script de desplegament#
Un desplegament típic segueix una seqüència d'operacions ben definida. Cada pas té un propòsit específic i l'ordre importa. A continuació, un script de desplegament complet amb explicacions:
#!/bin/bash
set -e # Aturar l'execució si qualsevol comanda falla
# Directori de l'aplicació
APP_DIR="/var/www/app"
cd $APP_DIR
# 1. Activar el mode de manteniment
# Mostra una pàgina de manteniment als usuaris mentre es desplega
php artisan down --retry=60 --refresh=15
# 2. Obtenir el codi més recent
git pull origin main
# 3. Instal·lar dependències PHP (sense dependències de desenvolupament)
composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
# 4. Instal·lar dependències JavaScript i compilar assets
npm ci
npm run build
# 5. Executar les migracions de base de dades
# --force és necessari en producció (Laravel demana confirmació per defecte)
php artisan migrate --force
# 6. Netejar i reconstruir totes les caches d'optimització
php artisan optimize:clear
php artisan optimize
# 7. Reiniciar els workers de cues perquè carreguin el codi nou
php artisan queue:restart
# 8. Recarregar PHP-FPM per netejar OPcache
sudo systemctl reload php8.3-fpm
# 9. Desactivar el mode de manteniment
php artisan upAlguns detalls importants sobre aquest script:
set -efa que l'script s'aturi si qualsevol comanda falla. Sense això, una migració fallida no impediria quephp artisan updesactivés el mode de manteniment, deixant l'aplicació en un estat inconsistent.npm cien lloc denpm install:ciinstal·la exactament les versions delpackage-lock.jsonsense modificar-lo, cosa que garanteix reproducibilitat.--forceamigrate: en producció, Laravel demana confirmació interactiva per defecte per evitar migracions accidentals. El flag--forceevita aquesta confirmació.queue:restartenvia un senyal als workers perquè acabin el job actual i es reiniciïn. No és instantani: cada worker es reinicia quan acaba el job que estigui processant.- La recàrrega de PHP-FPM (
reload, norestart) és una operació elegant que no interromp les peticions en curs. Les connexions existents acaben amb el procés antic i les noves connexions utilitzen el procés nou amb OPcache actualitzat.
Desplegament amb zero downtime#
L'script anterior té un problema: mentre s'executa, l'aplicació està en mode de manteniment. Si el desplegament triga 30 segons (instal·lar dependències, compilar assets, executar migracions), els usuaris veuran una pàgina de manteniment durant 30 segons. Per a aplicacions amb molt tràfic, això és inacceptable.
El desplegament amb zero downtime resol aquest problema amb una estructura de directoris basada en symlinks. La idea és senzilla: en lloc de modificar el codi en directe, es prepara una nova versió en un directori separat i, un cop tot està llest, s'intercanvia un symlink atòmicament. L'operació de canvi de symlink és instantània (microsegons), cosa que elimina completament el temps d'inactivitat.
L'estructura de directoris és la següent:
/var/www/app/
├── current -> releases/20250115120000 # Symlink a la versió activa
├── releases/
│ ├── 20250114100000/ # Versió anterior
│ ├── 20250115120000/ # Versió actual
│ └── 20250116090000/ # Nova versió (en preparació)
├── shared/
│ ├── .env # Configuració compartida
│ └── storage/ # Storage compartit entre versions
El directori current és un symlink que apunta a la versió activa. Cada desplegament crea un directori nou dins de releases/, hi prepara tot el codi, i finalment canvia el symlink current per apuntar a la nova versió. El directori shared/ conté els fitxers que han de persistir entre desplegaments (l'arxiu .env, el directori storage/ amb logs, fitxers pujats, etc.).
#!/bin/bash
set -e
RELEASE_DIR="/var/www/app/releases/$(date +%Y%m%d%H%M%S)"
SHARED_DIR="/var/www/app/shared"
CURRENT_LINK="/var/www/app/current"
# 1. Crear directori per a la nova versió
mkdir -p $RELEASE_DIR
# 2. Clonar el codi
git clone --depth 1 --branch main git@github.com:usuari/app.git $RELEASE_DIR
# 3. Enllaçar fitxers compartits
ln -sf $SHARED_DIR/.env $RELEASE_DIR/.env
rm -rf $RELEASE_DIR/storage
ln -sf $SHARED_DIR/storage $RELEASE_DIR/storage
# 4. Instal·lar dependències
cd $RELEASE_DIR
composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
npm ci && npm run build
# 5. Migracions i optimització
php artisan migrate --force
php artisan optimize
# 6. Canviar el symlink (operació atòmica)
ln -sfn $RELEASE_DIR $CURRENT_LINK
# 7. Reiniciar serveis
php artisan queue:restart
sudo systemctl reload php8.3-fpm
# 8. Netejar versions antigues (mantenir les últimes 5)
cd /var/www/app/releases
ls -dt */ | tail -n +6 | xargs rm -rfEines com Laravel Envoyer (servei oficial de Laravel per a desplegaments amb zero downtime) i Deployer (eina open-source) implementen aquesta estratègia automàticament amb funcionalitats addicionals com rollbacks, notificacions i health checks.
Health checks#
Després de cada desplegament, és important verificar que l'aplicació funciona correctament. Un health check bàsic comprova que l'aplicació respon, que la base de dades és accessible i que els serveis externs estan connectats:
// routes/web.php
Route::get('/health', function () {
$checks = [];
// Comprovar la base de dades
try {
DB::connection()->getPdo();
$checks['database'] = 'ok';
} catch (\Exception $e) {
$checks['database'] = 'error: ' . $e->getMessage();
}
// Comprovar Redis
try {
Cache::store('redis')->put('health-check', true, 10);
$checks['redis'] = 'ok';
} catch (\Exception $e) {
$checks['redis'] = 'error: ' . $e->getMessage();
}
// Comprovar l'storage
try {
Storage::put('health-check.txt', 'ok');
Storage::delete('health-check.txt');
$checks['storage'] = 'ok';
} catch (\Exception $e) {
$checks['storage'] = 'error: ' . $e->getMessage();
}
$allOk = collect($checks)->every(fn ($status) => $status === 'ok');
return response()->json([
'status' => $allOk ? 'healthy' : 'unhealthy',
'checks' => $checks,
'timestamp' => now()->toIso8601String(),
], $allOk ? 200 : 503);
});Després del desplegament, pots verificar l'estat amb una crida HTTP:
# Al final de l'script de desplegament
RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" https://lamevaaplicacio.com/health)
if [ "$RESPONSE" != "200" ]; then
echo "ALERTA: Health check ha fallat amb codi $RESPONSE"
# Aquí podries fer rollback automàticament
exit 1
fi
echo "Desplegament completat correctament"Checklist completa de producció#
Abans de cada desplegament, repassa aquesta checklist per assegurar-te que no oblides cap pas crític:
APP_ENV=productional fitxer.envAPP_DEBUG=falseper no exposar informació sensibleAPP_URLconfigurat amb el domini correcte i HTTPSAPP_KEYgenerat i configurat correctament- Base de dades configurada amb un usuari específic (no root)
- Cache, sessions i cues configurades amb Redis (o un driver adequat per a producció)
- Logs configurats amb el driver
dailyi nivellwarning composer install --no-dev --optimize-autoloaderper eliminar dependències de desenvolupamentnpm run buildper compilar i minificar els assetsphp artisan optimizeper cachear configuració, rutes, vistes i events- Permisos de
storage/ibootstrap/cache/correctes (775, propietat de www-data) - OPcache activat i configurat
- Nginx configurat amb HTTPS, gzip, cache d'estàtics i capçaleres de seguretat
- Certificat SSL instal·lat i renovat automàticament
- Cron configurat per al scheduler (
* * * * * php artisan schedule:run) - Workers de cues gestionats per Supervisor amb reinici automàtic
- Backups de base de dades configurats i verificats
- Monitorització activa (Sentry, Pulse o similar)
- Health check accessible i verificat
- Firewall configurat per bloquejar ports innecessaris