Laravel 9: deploy su hosting condiviso (senza SSH)

Sviluppare un'applicazione web con Laravel è un'esperienza gratificante. Tuttavia, il deploy su un hosting condiviso può risultare complicato, soprattutto se non si ha un accesso SSH a disposizione. In questo articolo, vi mostro la procedura che ho seguito: magari può essere utile a qualcun altro.

P.S. Se avete disponibile l'accesso SSH, vi consiglio di seguire invece questa guida del mio amico Marco Lancellotti.


Premessa

Lo spazio hosting messo a disposizione dal cliente è uno spazio condiviso con pannello di controllo cPanel, senza accesso SSH, con una struttura "rigida" e predefinita delle cartelle, in cui la cartella pubblica sia chiama public_html e non può essere rinominata.

Le cartelle del progetto

Lo scaffold standard di un progetto Laravel ha in genere questo aspetto:


  LaravelApp
  +-- app
  +-- bootstrap
  +-- config
  ...
  +-- database
  +-- public
  +-- resources
  ...
  +-- storage
  ...

e quindi come primo step è stato necessario rinominare la cartella public in public_html


  LaravelApp
  +-- app
  +-- bootstrap
  +-- config
  ...
  +-- database
  +-- public_html
  +-- resources
  ...
  +-- storage
  ...

Per separare la cartella pubblica dal resto, ho creato una nuova cartella laravel_core, spostandoci dentro tutto il resto:


  LaravelApp
  +-- laravel_core
  |   +-- bootstrap
  |   +-- config
  |   +-- ...
  |   +-- database
  |   +-- resources
  |   +-- ...
  |   +-- storage
  |   +-- ...
  +-- public_html

Le modifiche ai file

La modifica alla struttura delle cartelle del progetto ha comportato la necessità di modificare alcuni riferimenti anche nei file.
Nel file index.php presente nella cartella pubblica public_html ho modificato le righe


if (file_exists($maintenance = __DIR__ . '/../storage/framework/maintenance.php')) {
    require $maintenance;
}

...

require __DIR__ . '/../vendor/autoload.php';

...

$app = require_once __DIR__ . '/../bootstrap/app.php';

in 



if (file_exists($maintenance = __DIR__ . '/../laravel_core/storage/framework/maintenance.php')) {
    require $maintenance;
}

...

require __DIR__ . '/../laravel_core/vendor/autoload.php';

...

$app = require_once __DIR__ . '/../laravel_core/bootstrap/app.php';


Modifichiamo quindi il file AppServiceProvider.php presente in laravel_core\app\Providers integrando la funzione register in questo modo:


    public function register()
    {
        $this->app->bind('path.public', function () {
            return realpath(base_path() . '/../public_html');
        });
    }

Il mio progetto prevede l'utilizzo di Vite, quindi è stato necessario intervenire anche sul file vite.config.js nella cartella laravel_core, parametrizzando la cartella pubblica

export default defineConfig({
    plugins: [
        vue(),
        laravel({
            publicDirectory: '/../public_html', // <-- importante
            ...

Le modiche principali sono terminate e si possono testare avviando il progetto nel solito modo (attenzione, i comandi composer, npm e artisan vanno lanciati dalla cartella laravel_core):


   PS C:\www\xxxxx\laravel_core> npm run build    PS C:\www\xxxxx\laravel_core> php artisan serve

E i link simbolico ?

Purtroppo, senza altre modifiche, la generazione del link simbolico, necessario per gestire correttamente la cartella storage, produrrà un risultato diverso da quello atteso.
Per risolvere, ho modificato il parametro "link" presente nel file filesystems.php della cartella config


    'links' => [
        public_path('../../public_html/storage') => storage_path('app/public'),
    ],

Nota per Laravel 10

Se state usando Laravel 10 (grazie al mio amico Umberto per la segnalazione), la modifica per il link simbolico non risulta necessaria, ma occorre una modifica al file app.php presente in laravel_core\bootstrap aggiungendo la seguente riga


$app->usePublicPath(realpath(base_path('/../public_html')));


immediatamente dopo la definizione della variabile $app.

Nota per Laravel 11

Se state usando Laravel 11, la modifica al file app.php presente in laravel_core\bootstrap è questa:


$app = Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__ . '/../routes/web.php',
        api: __DIR__ . '/../routes/api.php',
        commands: __DIR__ . '/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->statefulApi();
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

$app->usePublicPath(realpath(base_path('/../public_html')));

return $app;

Carichiamo i file nello spazio

Non ci resta che caricare i file nello spazio condiviso.
In alternativa al classico FTP, usando la gestione file messa a disposizione da cPanel, basta creare un file zip con tutto il contenuto della cartella public_html (compresa la cartella build generata nei passaggi precedenti) e un altro con tutto il contenuto di laravel_core, caricarli e estrarli nelle rispettive cartelle dello spazio condiviso, ottenendo un risultato simile al mio:


La configurazione finale

Dopo aver modificato il file .env con i parametri del nostro server (connessione al db, parametri mail, ...) restano da avviare i comandi artisan per le migrazioni e per la creazione del link simbolico.
Non avendo a disposizione il canale SSH, basta creare una rotta temporanea (modificando il file web.php nella cartella routes


Route::get('/setup', function () {
    Artisan::call('migrate:refresh', ['--seed' => true,]);
    Artisan::call('storage:link', ['--force' => true,]);
});

In questo modo, basterà visitare questa rotta per avviare i comandi di base (e con una procedura simile sarà possibile avviare in seguito anche eventuali migrazioni di aggiornamento).

Chiaramente la rotta va rimossa dopo l'utilizzo.

Conclusioni

Con un po' di attenzione ai dettagli, è possibile deployare un progetto Laravel su un hosting condiviso senza accesso SSH. Seguendo le istruzioni di questo articolo, anche voi sarete in grado di mettere online il vostro sito web in pochissimo tempo.

Commenti