Un serveur Web incluant généralement un logiciel serveur (Nginx), un interpréteur de script CGI (PHP) et d’un système de gestion de base de données (PostgreSQL), nous allons voir comment mettre en place ces trois composants en utilisant Docker.

Nginx et PHP FPM

Récemment, j’expliquais comment simplement mettre en place un serveur Nginx qui supporte PHP à l’aide de Docker. L’image php:fpm-alpine utilisée y était minimaliste et n’embarquait que très peu d’extension PHP. De plus, aucune base de données n’était présente dans notre infra : c’est ce point là que nous allons corrigé ici.

Pour rappel, nous avions créé l’arborescence suivante dans laquelle nous retrouvons le fichier de configuration nginx.conf, les journaux Nginx et le contenu du site Web dans www :

├── docker-compose.yml
└── docker-web
    ├── log
    │   ├── access.log
    │   └── error.log
    ├── nginx.conf
    └── www
        └── index.php
docker-compose.yml
version: '3'

services:
    web-nginx:
        image: nginx:stable-alpine
        container_name: web-nginx
        volumes:
            - "./docker-web/www:/usr/share/nginx/html:ro"
            - "./docker-web/log:/var/log/nginx"
            - "./docker-web/nginx.conf:/etc/nginx/nginx.conf:ro"
        ports:
            - "127.0.0.1:80:80"

    web-php:
        image: php:fpm-alpine
        container_name: web-php
        volumes:
            - "./docker-web/www:/script:ro"
docker-web/nginx.conf
user                       nginx;
worker_processes           1;

error_log                  /var/log/nginx/error.log warn;
pid                        /var/run/nginx.pid;

events {
    worker_connections     1024;
}

http {
    include                /etc/nginx/mime.types;
    default_type           application/octet-stream;
    
    log_format             main  '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$http_x_forwarded_for"';
    access_log             /var/log/nginx/access.log main;
    
    sendfile               on;
    keepalive_timeout      65;
    server_tokens          off;

    server {
        listen             80;
        server_name        localhost;
        
        location / {
            root           /usr/share/nginx/html;
            index          index.php index.html index.htm;
        }
        
        error_page         500 502 503 504 /50x.html;
        location = /50x.html {
            root           /usr/share/nginx/html;
        }

        location ~ \.php$ {
            root           /usr/share/nginx/html;
            include        fastcgi_params;
            fastcgi_pass   web-php:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /script$fastcgi_script_name;
        }
    }
}

Après avoir vérifié que nos deux containers web-nginx et web-php sont bien démarrés et que le serveur Nginx est accessible depuis un navigateur Web, nous allons modifier le fichier index.php pour qu’il se connecte à la base de données et affiche la version du SGBD.

docker-web/www/index.php
<?php
$dbconn = pg_connect('host=web-pgsql port=5432 dbname=foobar user=foobar password=foobar')
    or die('Could not connect');
     
echo '<pre>' . var_export(pg_version($dbconn), true) . '</pre>';

pg_close($dbconn);
?>

Si tous ce passe bien, vous devriez obtenir une erreur … wait, what?

Call to undefined function

Personnaliser l’image Docker de PHP

Le serveur nous indique qu’il y a une erreur : Call to undefined function pg_connect(). En effet, l’image Docker standard de PHP n’embarque que très peu de module. Il est précisé sur la page DockerHub PHP comment ajouter des extensions PHP supplémentaires. Nous allons donc créer notre propre image Docker de PHP pour y intégrer le client PostgreSQL.

Commençons par créer un fichier Dockerfile dans le répertoire docker-web/.build-php (vous pouvez le placer ailleurs). C’est dans ce fichiers que nous allons décrire comment construire l’image :

docker-web/.build-php/Dockerfile
FROM php:fpm-alpine
RUN apk update && apk add --no-cache \
        postgresql-dev \
    && docker-php-ext-install -j$(nproc) pgsql \
    && docker-php-ext-install -j$(nproc) pdo_pgsql

Il faut également modifier le fichier docker-compose.yml pour préciser que nous voulons utiliser notre image plutôt que php:fpm-alpine. Nous renseignons pour cela la directive build afin d’indiquer à Docker Compose de construire l’image si nécessaire (premier démarrage, modification du fichier Dockerfile, etc…), ou d’utiliser la dernière image construite si aucune modification n’a été opérée depuis le dernier build.

web-php:
    build: ./docker-web/.build-php
    container_name: web-php
    volumes:
        - "./docker-web/www:/script:ro"
Console
host:~# docker-compose up -d

Si tous ce passe bien, vous devriez obtenir une erreur … wait, again?

Unable to connect to PostgreSQL server

Le container PostgreSQL

Évidement, il nous manque le container PostgreSQL, que nous avons déjà nommé ci-dessus dans le fichier index.php : web-pgsql.

Console
host:~# mkdir -p docker-web/data # Pour stocker le contenu de la base de données

Pas besoin de faire compliqué ici, nous allons simplement utiliser l’image officielle et Docker Compose pour configurer et démarrer le container PostgreSQL :

docker-compose.yml
version: '3'

services:
    web-nginx:
        image: nginx:stable-alpine
        container_name: web-nginx
        volumes:
            - "./docker-web/www:/usr/share/nginx/html:ro"
            - "./docker-web/log:/var/log/nginx"
            - "./docker-web/nginx.conf:/etc/nginx/nginx.conf:ro"
        ports:
            - "127.0.0.1:80:80"

    web-php:
        image: php:fpm-alpine
        container_name: web-php
        volumes:
            - "./docker-web/www:/script:ro"

    web-pgsql:
        image: postgres:alpine
        container_name: web-pgsql
        volumes:
            - "./docker-web/data:/var/lib/postgresql/data"
        environment:
            POSTGRES_USER: foobar
            POSTGRES_PASSWORD: foobar
            POSTGRES_DB: foobar
Console
host:~# docker-compose up -d

Si tous ce passe bien, vous devriez obtenir une erreur … Ah non, pas cette fois-ci, maintenant que tout est en place la fonction pg_version() renvoie les infos des versions client/serveur de PostgreSQL.

Tout fonctionne

Références

  1. PHP (Docker Official Images) - Docker Hub
  2. Nginx (Docker Official Images) - Docker Hub
  3. PostgreSQL (Docker Official Images) - Docker Hub