Usare Docker per una applicazione Laravel

Capita sempre più spesso di avere più progetti Laravel in corso che richiedono versioni e configurazioni diverse di PHP o semplicemente non sempre si può aggiornare il proprio sistema LAMP per un semplice test. Per questo motivo ho iniziato a lavorare con Docker per separare le diverse installazioni. In questo articolo, vi propongo il processo di dockerizzazione di un'applicazione Laravel, in ambiente Windows, i comandi essenziali e le configurazioni ottimali per sfruttare appieno il potenziale di Docker con Laravel.

Docker

Nell'era dello sviluppo software agile e della distribuzione continua, Docker è sicuramente uno degli strumenti indispensabile per un developer. La sua capacità di containerizzare le applicazioni, garantendo consistenza e isolamento tra ambienti, ha rivoluzionato il modo in cui sviluppiamo e distribuiamo il software.

Laravel, uno dei framework PHP più amati, non fa eccezione. Integrare Docker nel flusso di lavoro di Laravel offre numerosi vantaggi:

  • Ambienti di sviluppo coerenti: Dimentica i problemi di "funziona sulla mia macchina". Docker assicura che il tuo ambiente di sviluppo sia identico a quello di produzione.
  • Distribuzione semplificata: Impacchetta la tua applicazione Laravel e le sue dipendenze in un singolo contenitore, facilitando la distribuzione su qualsiasi infrastruttura.
  • Scalabilità e gestione delle dipendenze: Gestisci facilmente le dipendenze e scala la tua applicazione senza preoccupazioni.
Se non lo avete già fatto, installate Docker Desktop seguendo le guide ufficiali.

Un container per Laravel 

Per gli scopi di questo articolo partiamo da una applicazione Laravel esistente e creiamo una cartella docker che conterrà tutto quello che ci serve.


Per avere un ambiente di sviluppo adatto a Laravel, ci occorre un server http, un interprete PHP, un servizio mySql. A questo stack minimo, noi aggiungeremo il tool phpMyAdmin e attiveremo Xdebug sul PHP.

Questo metodo è chiaramente alternativo all'utilizzo di Laravel Sail, ma mio avviso è più versatile e permette di gestire più facilmente l'ambiente di sviluppo con un utilizzo diretto di Docker (tecnologia che può facilmente essere riutilizzata) senza intermediazioni.

Il server Nginx

Nella cartella creiamo un file docker-compose.yml e nel file iniziamo a inserire le definizioni dei container necessari (attenzione: in questo tipi di file è necessario usare il tab per l'indentazione):


services:

  nginx:
    image: nginx:latest
    container_name: nginx
    volumes:
      - ./../:/var/www/html
      - ./default.conf:/etc/nginx/conf.d/default.conf
    ports:
      - "8080:80"
    links:
      - phpfpm

Le righe precedenti, definiscono un server nginx a partire dall'immagine più recente disponibile, aggiunge due volumi (uno con la root del nostro progetto e un altro con un file di configurazione che vedremo tra poco), espone la porta 80 del container sulla porta 8080 del nostro host, infine indica che questo servizio dipende da un servizio phpfpm che ancora dobbiamo definire.

Nella stessa cartella docker, creiamo un file default.conf con i parametri di configurazione del server  nginx

  • indicazione della porta
  • indicazione della cartella di root
  • indicazione del file di avvio (index.php, prioritario rispetto a index.html)
  • ...


server {
  listen 80;
  charset utf-8;
  root /var/www/html/public;
  index index.php index.html;
  error_log  /var/log/nginx/error.log;
  access_log /var/log/nginx/access.log;
  location = /favicon.ico { access_log off; log_not_found off; }
  location = /robots.txt  { access_log off; log_not_found off; }
  location ~ \.php$ {
    try_files $uri =404;
    fastcgi_pass phpfpm:9000;
    fastcgi_index index.php;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    include fastcgi_params;
  }
}

L'interprete PHP

Nel file docker-compose.yml aggiungiamo la definizione di un nuovo container:


  phpfpm:
    container_name: phpfpm  
    build:
      context: ./..
      dockerfile: docker/Dockerfile
    volumes:
      - ./../:/var/www/html
      - ./xdebug.ini:/usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
    links:
      - mysql


La definizione di questo container è spostata in un Dockerfile specifico. anche questo servizio aggiunge due volumi (uno con la root del nostro progetto e un altro con un file di configurazione di xdebug che vedremo tra poco), infine indica che questo servizio dipende da un servizio mysql che ancora dobbiamo definire. 

Nella stessa cartella docker, creiamo un file Dockerfile con i parametri di configurazione del nostro container phpfpm:


FROM php:8.2-fpm-alpine
RUN apk update \
 && apk upgrade \
 && apk add \
        linux-headers \
        $PHPIZE_DEPS \
 && pecl install xdebug \
 && docker-php-ext-enable xdebug \
 && docker-php-ext-install pdo_mysql \
 && apk del $PHPIZE_DEPS \
 && rm -rf /var/cache/apk/*


Il container quindi è basato sull'immagine PHP 8.2 Fpm Alpine (ma la versione Laravel su cui state lavorando potrebbe richiede una versione diversa di PHP), a cui è aggiunto Xdebug e il supporto per mysql. 

Per la configurazione di Xdebug, sempre nella cartella docker, creiamo un file xdebug.ini:


zend_extension=xdebug.so
xdebug.mode=debug,develop
xdebug.start_with_request=yes
xdebug.discover_client_host=1
xdebug.cli_color=1
xdebug.log_level=0

Il server MySQL

Nel file docker-compose.yml aggiungiamo la definizione di un nuovo container:


  mysql:
    image: mysql:latest
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: app_db
    ports:
      - "6033:3306"
    volumes:
      - ./.data:/var/lib/mysql  


Le righe precedenti, definiscono un server mysql a partire dall'immagine più recente disponibile, definisce la password di root e il nome del database da creare (se non già presente), espone la porta 3306 del container sulla porta 6033 del nostro host, infine aggiunge un volume (la sottocartella .data della nostra cartella docker che conterrà i dati del database in modo da garantire la persistenza anche se distrugge e si ricostruisce il container.

Il tool phpMyAdmin

Sempre nel file docker-compose.yml infine aggiungiamo la definizione di un nuovo container per phpMyadmin, esposto sulla porta 8081 del nostro host. anche questo servizio dipende dal servizio mysql:


  phpmyadmin:
    image: phpmyadmin:latest
    container_name: pma
    environment:
      PMA_HOST: mysql
      PMA_PORT: 3306
      PMA_ARBITRARY: 1
    restart: always
    ports:
      - 8081:80      
    links:
      - mysql

La configurazione di Laravel

Adeguiamo la configurazione di Laravel per la corretta connessione al database, quini nel file .env, modifichiamo le righe seguenti:


DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=app_db
DB_USERNAME=root
DB_PASSWORD=root

Avviamo il container 

La definizione di quanto necessario è terminata, non ci resta quindi che avviare la creazione del container. Da terminale, posizioniamoci nella cartella docker che abbiamo creato e avviamo il seguente comando, sostituendo nome_progetto con il nome che vogliamo assegnare al nostro container:


docker-compose -f docker-compose.yml -p nome_progetto pull && \
docker-compose -f docker-compose.yml -p nome_progetto build --no-cache && \
docker-compose -f docker-compose.yml -p nome_progetto up -d --remove-orphans


Salvo errori, in Docker Desktop dovreste avere vista simile a questa:

E' possibile raggiungere la homepage del progetto su localhost:8080 e phpMyAdmin all'indirizzo localhost:8081 (o in alternativa cliccando in corrispondenza delle porte indicate):


Se fosse necessario modificare la configurazione dei container sarà poi necessario ricostruirli. Automatizziamo il processo, creando un file batch (siamo in ambiente Windows) con tutto quanto necessario per distruggere il container e ricostruirli, velocemente. sempre nella cartella docker, creiamo un file create.bat con le seguenti istruzioni:

 
  SET NOME_PROGETTO=laravel_test
 
  docker-compose -f docker-compose.yml -p %NOME_PROGETTO% down
  docker-compose -f docker-compose.yml -p %NOME_PROGETTO% rm -f
  docker-compose -f docker-compose.yml -p %NOME_PROGETTO% pull
  docker-compose -f docker-compose.yml -p %NOME_PROGETTO% build --no-cache
  docker-compose -f docker-compose.yml -p %NOME_PROGETTO% up -d --remove-orphans
  docker image prune -f --filter="dangling=true"


Ogni volta che avvieremo il file batch, avremo che:
  • interrompe e rimuove i container esistenti e le reti create
  • cancella i container dei servizi stoppati
  • scarica le immagini necessarie
  • fa la build dei servizi (ignorando la cache in modo da essere sicuri di avere sempre immagini recenti)
  • crea e avvia i container, rimuovendo le dipendenze non più necessarie
  • rimuove le immagini non più utili e non collegate ad altre immagini
Naturalmente questi comandi non cancellano i dati del nostro database, che sono al sicuro nella cartella docker/.data, che magari sarebbe utile da escludere da una eventuale sincronizzazione github.

I comandi artisan 

Per completare l'ambiente di sviluppo Laravel, non ci resta che gestire i comandi artisan. Questi vanno lanciati nell'ambiente docker, quindi da terminale. Ad esempio per le migrazioni occorre digitare:


  docker exec phpfpm php artisan migrate


e in modo similare tutti gli altri che servono.  

Conclusioni

Se volte approfondire gli argomenti trattati, vi consiglio alcuni video pubblicati sul canale Youtube di Manuel Zavatta che ho utilizzato come base per questo articolo. 
I file di configurazione così come descritti li potete trovare
tra le mie repo GitHub: https://github.com/luigimicco/docker_for_laravel

Commenti