Dale M. Picou, Jr.

Docker Image: PHP with MySQLi

Introduction

Hypertext Preprocessor (PHP) has been a staple in web development for years. PHP has been part of my web development since the beginning. Originally I ran the service embedded in Apache but when migrating to Docker it was time for a change. Instead of being coupled with Apache, it was time to make the switch to FastCGI Process Manager (FPM) and a separate NGINX container. After a few days experimentation, the PHP docker image with the MySQLi extension was born.

Since most people like to build the images themselves, I have the full script to build the image available on gitlab or one might take the easy way out and pull my image: djpic/php.

What is the Image?

This Docker image is sourced from the Official Docker PHP FPM Alpine image. The current build version is 7.4.x. For the exact PHP version being used, see the Docker Hub: djpic/php. The image includes the MySQLi and memcached extension along with the optional browscap files. The browscap files are updated every time a new image is built even if the PHP version is not updated.

How to use the image

The image is designed to be used with my NGINX image (djpic/nginx) but can be used with any FPM web server. All PHP files should be copied/mounted into the same location as the corresponding web server. For example, if using the default NGINX settings, /usr/share/nginx/html. The memcached extension is included as a method to cache database responses and/or to store temporary data. Like MySQLi, it is an extension and requires an instance of memcached.

Image Tags

There are a total of 4 different tags/variants on the image: mysqli, mysqli-browscap, mysqli-lite_php_browscap, and mysqli-full_php_browscap.

mysqli
As the tag suggest, this variant includes the MySQLi and memcached extension for PHP. This image will be the best for the majority of applications.
mysqli-browscap
This variant includes MySQLi and memcached extensions along with the special version of browscap file for PHP users only. The updated php.ini file to use the browscap file is included.
mysqli-lite_php_browscap
This variant includes MySQLi and memcached extensions along with the lite, smaller, browscap file. The updated php.ini file to use the browscap file is included.
mysqli-full_php_browscap
This variant includes MySQLi and memcached extensions along with the full browscap file. The updated php.ini file to use the browscap file is included.

Build the Image

All docker builds start with a Dockerfile. The primary Dockerfile for this image is where the base PHP image is pulled from the Official Docker PHP alpine image. When changing the base PHP image of all variants for the image, update the FROM in this Dockerfile. The MySQLi extension and memcached extension is installed into the image at this stage.

FROM php:7.4.15-fpm-alpine

RUN docker-php-ext-install mysqli

ENV MEMCACHED_DEPS zlib-dev libmemcached-dev cyrus-sasl-dev
RUN apk add --no-cache --update libmemcached-libs zlib
RUN set -xe \
    && apk add --no-cache --update --virtual .phpize-deps $PHPIZE_DEPS \
    && apk add --no-cache --update --virtual .memcached-deps $MEMCACHED_DEPS \
    && pecl install memcached \
    && echo "extension=memcached.so" > /usr/local/etc/php/conf.d/20_memcached.ini \
    && rm -rf /usr/share/php7 \
    && rm -rf /tmp/* \
    && apk del .memcached-deps .phpize-deps

The next Dockerfile is for the browscap versions. The browscap Dockerfile uses the previous MySQLi image variant as a base. This Dockerfile copies in the browscap file and the updated php.ini file. The same Dockerfile is used for each browscap variant.

FROM djpic/php:mysqli

COPY php.ini /usr/local/etc/php/php.ini
COPY browscap.ini /usr/local/etc/php/browscap.ini

Below is the updated line in the php.ini file.

[browscap]
browscap = /usr/local/etc/php/browscap.ini

Finally comes the glue that sticks everything together: build.sh. Following removing any old images, the shell script begins to build the MySQLi image first. From there, the script continues to on to the browscap variants. The script pulls the latest browscap file, runs the browscap Dockerfile, then removes the browscap file that was pulled. This is repeated 2 more times for each browscap variant.

Once all image variants are completed, they are pushed to the DockerHub then removed from the build machine.

#!/bin/bash

# Pre Clean
docker rmi djpic/php:mysqli
docker rmi djpic/php:mysqli-browscap
docker rmi djpic/php:mysqli-full_php_browscap
docker rmi djpic/php:mysqli-lite_php_browscap

# Build apache image with mysqli and memcached extension
cd MySQLi
docker build --tag djpic/php:mysqli .

# Build image with browscap file
cd browscap
wget -O browscap.ini https://browscap.org/stream?q=PHP_BrowsCapINI
docker build --tag djpic/php:mysqli-browscap .
rm browscap.ini

wget -O browscap.ini https://browscap.org/stream?q=Full_PHP_BrowsCapINI
docker build --tag djpic/php:mysqli-full_php_browscap .
rm browscap.ini

wget -O browscap.ini https://browscap.org/stream?q=Lite_PHP_BrowsCapINI
docker build --tag djpic/php:mysqli-lite_php_browscap .
rm browscap.ini

# Docker Push Image
docker push djpic/php:mysqli
docker push djpic/php:mysqli-browscap
docker push djpic/php:mysqli-full_php_browscap
docker push djpic/php:mysqli-lite_php_browscap

# Docker Clean Up
docker rmi djpic/php:mysqli
docker rmi djpic/php:mysqli-browscap
docker rmi djpic/php:mysqli-full_php_browscap
docker rmi djpic/php:mysqli-lite_php_browscap

Sample docker-compose file

Since both my djpic/php and djpic/nginx images are designed to work together, I provided an example of a docker-compose file using both images. The two containers use the back end network to communicate. I do use a custom image of Traefik, so this docker-compose file does include djpic/traefik labels.

version: "3.2"
services:
  nginx:
    image: djpic/nginx:phpfpm
    networks:
      - FrontEnd
      - BackEnd
    depends_on:
      - php
    volumes:
      - ./application/:/app
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.djpicdemo-web.rule=Host(`demo.djpic.net`)"
      - "traefik.http.routers.djpicdemo-web.entrypoints=web"
      - "traefik.http.routers.djpicdemo-web.middlewares=https-redirect@file"
      - "traefik.http.routers.djpicdemo-tls.rule=Host(`demo.djpic.net`)"
      - "traefik.http.routers.djpicdemo-tls.entrypoints=websecure"
      - "traefik.http.routers.djpicdemo-tls.tls.certresolver=letsencrypt"
      - "traefik.http.routers.djpicdemo-tls.middlewares=secure-headers@file, compress-content@file"
      #- "traefik.http.services.djpicdemo.loadbalancer.server.port=443"
      #- "traefik.http.services.djpicdemo.loadbalancer.server.scheme=https"
    restart: always

  php:
    image: djpic/php:mysqli
    networks:
      - BackEnd
    volumes:
      - ./application/:/app

  memcached:
    image: library/memcached:alpine
    networks:
      - BackEnd
    command: ["-m", "64m"]
    restart: always

networks:
  FrontEnd:
    external: true
  BackEnd:

Conclusion

Fully built images are available and free to use on my Docker Hub. In addition, the source files listed here are available along with my other public docker images on gitlab. Feel free to download, modify, and reuse. Just remember the change the image names.

This image has proved invaluable to my development. I use is for every project PHP is needed. More variants may be added to this image as the need arises. When that happens, this article will be updated.

I welcome all constructive input. If anything is incorrect or needs more explanation, feel free to contact me.

Disclaimer: The contents of this article are of my opinion and based on my experience. Before applying any of the concepts or suggestions in this article, complete your own independent research and testing. By reading this article, the reader agrees that Dale M. Picou, Jr. shall not be held liable for any negative impact when applying these tutorials, concepts, and/or suggestions.