La Containerization (che in italiano dovrebbe corrispondere alla parola bruttina Containerizzazione) è la tecnologia che negli ultimi anni è sicuramente al centro dell’attenzione ed è entrata a far parte del bagaglio tecnico di qualsiasi developer, perché permette di sviluppare meglio e più in fretta. La sua architettura molto pulita ci permette di sfruttare al meglio le risorse (hardware) scomponendo un’applicazione in servizi più piccoli chiamati images, immagini.

In questo mini-tutorial, analizzeremo un modello di file docker-compose che ci permetterà di settare l’ambiente della nostra applicazione e lanciare tutti i comandi necessari per completare il setup, con un approccio semplice ed elegante.

Come da titolo, vedremo come fare il setup per un ambiente perfetto per un’applicazione PHP servita da Apache, con il supporto del classico database MySQL e senza dimenticare un tool importante come phpMyAdmin.

Con Docker, un classico ambiente configurato in questo modo è assai gestibile e facile da rendere subito operativo. Assomiglia sì al classico environment LAMP, ma con una portabilità ed una semplicità di gestione molto maggiori. Ognuno dei servizi girerà come un microservice, in questo modo potremmo creare un solo file YML (il docker-compose.yml) che creerà i vari servizi, che resteranno isolati fra di loro.

Il vantaggio principale dei Containers, è la scalabilità che essi offrono, sia per quanto riguarda un eventuale flusso CI/CD sia per la condivisione delle immagini tra tutto il team di sviluppo. Basterà condividere il file docker-compose.yml inserendolo nel repository Git, così ciascuno potrà far girare un ambiente identico per sviluppare e testare l’applicazione, indipendentemente dal sistema operativo sul quale si lavora.

In questa guida vedremo:

  • Creare un container locale con PHP+Apache
  • Testarlo con un semplice script PHP
  • Creare un database MySQL e testarlo con PHP

Utilizzeremo l’archivio pubblico delle immagini, il Docker hub, per il nostro container.

Prerequisiti

  • Assicuriamoci di aver installato l’applicazione Docker sul nostro computer 
  • Conoscenza basilare di PHP e un minimo di SQL
  • Conoscenza basilare di Docker e di come funziona un container
  • Conoscenza base dei comandi linux e dei comandi docker-compose

Installiamo e lanciamo Docker

Dopo aver scaricato ed installato Docker, lanciamo l’applicazione desktop per assicurarci che l’engine sia in esecuzione.

Per saperne di più, apriamo il terminale sul nostro computer ed eseguiamo il comando:

docker version

Avremo una risposta simile a questa:

Luca-Murante:~ luca$ docker version
Client:
 Cloud integration: v1.0.22
 Version:           20.10.11
 API version:       1.41
 Go version:        go1.16.10
 Git commit:        dea9396
 Built:             Thu Nov 18 00:36:09 2021
 OS/Arch:           darwin/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.11
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.9
  Git commit:       847da18
  Built:            Thu Nov 18 00:35:39 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.12
  GitCommit:        7b11cfaabd73bb80907dd23182b9347b4245eb5d
 runc:
  Version:          1.0.2
  GitCommit:        v1.0.2-0-g52b36a2
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

Proviamo, a questo punto, a visualizzare i container attivi, naturalmente l’elenco sarà vuoto:

Il file docker-compose.yml

Questo file ci permetterà di avere una configurazione stabilita nel nostro progetto in modo pragmatico. Ci permetterà, nello specifico di configurare i servizi e di replicare il nostro container all’infinito, basterà appunto includere il file docker-compose.yml nel progetto. Nel nostro caso, utilizzeremo immagini ufficiali provenienti dal Docker hub, come PHP Apache e MySQL.

Creiamo quindi nel nostro progetto un file chiamato docker-compose.yml.

Per prima cosa, indicheremo che versione di Docker compose intenderemo utilizzare. Al momento della scrittura di questa guida, l’ultima versione è la 3.8. Iniziamo poi a definire i vari servizi che useremo e, passo passo, aggiungeremo alcune direttive per configurarli.

version: '3.8'
services:
    php-apache-environment:
    container_name:
 

Setup di PHP e Apache

Per il setup del nostro container PHP + Apache, impostiamo alcune direttive “base”:

• Il nome del container: è soltanto il nome che ci servirà per alcune operazioni, non indicandolo Docker assegnerà un nome casuale. Utilizzeremo php-apache

• L’immagine di partenza di quel servizio: utilizzeremo una delle immagini ufficiali presenti sul repository Docker Hub. In questo caso, il nostro servizio utilizzerà php:8.0-apache

• Il volume: questa direttiva imposta la corrispondenza tra la nostra working directory e la directory nel container corrispondente a quella di Apache

Nel nostro caso:

volumes:
    - ./php/src:/var/www/html/ 

• Le porte sulle quali sarà disponibile il servizio:

ports:
    - 8000:80

In questo caso significa che il servizio risponde alla porta 80 all’interno del container, ma espone verso di noi la porta 8000. È questa la porta che visiteremo dal nostro browser, es. localhost:8000

Ecco una prima versione del nostro docker-compose.yml:

version: '3.8'
services:
    php-apache-environment:
      container_name: php-apache
      image: php:8.0-apache
      volumes:
        - ./php/src:/var/www/html/
      ports:
        - 8000:80
 

Testiamolo subito: lanciamo il comando docker-compose up. Docker scaricherà automaticamente le immagini dal repository e lancerà i container del nostro file.

Aprendo Docker desktop, vedremo i container up and running:

Come vi anticipavo, adesso possiamo puntare il browser a http://localhost:8000

Sì, è un errore, ma significa che php-apache sta girando!

Facciamo girare un po’ di PHP

Nel nostro file docker-compose.yml abbiamo indicato come volume questo:

volumes:
    - ./php/src:/var/www/html/

Significa cioè che i file del nostro progetto dovranno essere nella sottodirectory php/src.

Creiamo un file index.php:

<?php
echo "Hello there, this is a PHP Apache container";
?>

Aggiorniamo la pagina:

Ci siamo! Abbiamo containerizzato il nostro progetto PHP.

Setup del container MySQL

A questo punto aggiungiamo al nostro progetto il servizio MySQL, in modo da poterci garantire l’utilizzo di un database con PHP.

Sempre all’interno del nostro file docker-compose.yml, aggiungiamo una nuova voce db. Impostiamo inoltre l’autenticazione (username, password, password di root) per il servizio.

Aggiungiamo la direttiva restart: always in modo da far ripartire il servizio ogniqualvolta facciamo un cambiamento nel file.

db:
    container_name: db
    image: mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: MYSQL_ROOT_PASSWORD
      MYSQL_DATABASE: MY_DATABASE
      MYSQL_USER: MYSQL_USER
      MYSQL_PASSWORD: MYSQL_PASSWORD
    ports:
      - "9906:3306"

Creiamo un Dockerfile

Docker può creare automaticamente le immagini partendo da un Dockerfile. Un Dockerfile è un semplice file di testo che contiene dei comandi, proprio come quelli che daremmo noi da terminale, che servono ad “assemblare” un’immagine. 

Nel nostro caso, ci servirà per esempio che l’interprete PHP “contenuto” nell’immagine php:8.0-apache specificata nel nostro docker-compose.yml abbia la capacità di connettersi a mysql: per questo motivo dobbiamo installare alcune librerie. Proprio come se fossimo in una macchina virtuale, possiamo farlo specificando alcuni comandi nel nostro Dockerfile. Per prima cosa, quindi, andiamo a creare un file vuoto chiamato appunto Dockerfile (senza estensione). Aggiungiamo:

FROM php:8.0-apache
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
RUN apt-get update && apt-get upgrade -y

A questo punto, introduciamo una nuova direttiva nel nostro docker-compose.yml, ovvero da depends_on:. Questa direttiva dice ad ogni servizio che dipende da un altro, sempre presente nel file, e che quindi va lanciato in giusta sequenza.

Ecco, a questo punto, come dovrebbe essere il nostro docker-compose.yml:

version: '3.8'
    services:
      php-apache-environment:
        container_name: php-apache
        build:
          context: ./php
          dockerfile: Dockerfile
        depends_on:
          - db
        volumes:
          - ./php/src:/var/www/html/
        ports:
          - 8000:80
    db:
      container_name: db
      image: mysql
      restart: always
      environment:
        MYSQL_ROOT_PASSWORD: MYSQL_ROOT_PASSWORD
        MYSQL_DATABASE: MYSQL_DATABASE
        MYSQL_USER: MYSQL_USER
        MYSQL_PASSWORD: MYSQL_PASSWORD
    ports:
      - "9906:3306"

Lanciamo il comando docker-compose up per fare il pull delle immagini e lanciare i container. Adesso dovremmo vedere anche il servizio MySQL attivo nel nostro Docker Desktop:

Scriviamo un po’ di PHP

A questo punto, modifichiamo il nostro file index.php per testare la connessione al servizio MySQL conteinerizzato che abbiamo appena lanciato:

<?php
// index.php

// Il nome del servizio definito in >docker-compose.yml.
$host = 'db';

$user = 'MYSQL_USER';

$pass = 'MYSQL_PASSWORD';

$conn = new mysqli($host, $user, $pass);
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
} else {
   echo "Connected to MySQL server successfully!";
}
?>
 

Salviamo e refreshiamo la pagina http://localhost:8000 nel browser:

Fatto, adesso che PHP Apache e MySQL sono up e funzionano siamo pronti per sviluppare.

Installiamo phpMyAdmin

Adesso che la connessione a MySQL è fatta, potrebbe essere molto (ma molto) utile avere phpMyAdmin per gestire il database. Anche per lui c’è un’immagine già pronta da utilizzare, dovremmo soltanto configurarla un minimo e sarà pronta.

Aggiungiamo al nostro docker-compose.yml una nuova sezione:

phpmyadmin:
    image: phpmyadmin/phpmyadmin
    ports:
      - '8080:80'
    restart: always
    environment:
      PMA_HOST: db
    depends_on:
      - db

Come vedi abbiamo specificato di voler utilizzare l’immagine phpmyadmin/phpmyadmin, di mappare la porta alla 8080 e di utilizzare il servizio MySQL specificato in  PMA_HOST: db

Eseguiamo di nuovo docker-compose up e per fare il pull e l’esecuzione di questo nuovo container.

Puntiamo il browser verso http://localhost:8080 e dovremmo vedere la pagina di login di phpMyAdmin.

Come loggarci? Con nome utente e password impostate nel docker-compose.yml nella sezione db. (MYSQL_USER e MYSQL_PASSWORD)

Conclusioni

Con questo mini tutorial abbiamo visto come in poche decine di minuti possiamo containerizzare il nostro progetto PHP/Apache e MySQL. Questo è solo un punto di partenza, perché Docker ci permetterà di ottenere grandi benefici, pensiamo ad esempio ad aggiungere un servizio come Redis al nostro progetto o semplicemente alla sua portabilità: includiamo docker-compose.yml e Dockerfile nel nostro repository git e saremo in grado di riprendere lo sviluppo del nostro progetto ovunque ci troviamo.