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 (
Since most people like to build the images themselves, the full script to build the image available on GitLab or one might take the easy way out and pull the pre-built image: djpic/php.
What is the Image?
This Docker image is sourced from the Official Docker PHP
The image includes Composer along with the MySQLi and memcached extensions. Optional are the browscap files. The browscap files are updated every time a new image is built even if the
How to use the image
The image is designed to be used with my NGINX image (djpic/nginx) but can be used with any
The memcached extension is included as a method to cache database responses and/or to store temporary data. Like MySQLi, memcached is an extension and requires a memcached container.
As a bonus Composer is installed to manage application dependencies. This gives the option of building an image with common
Example of Dockerfile installing AWS S3 SDK:
FROM djpic/php:8.1.12-mysqli
# Install mailgun and required packages
RUN cd /composer/ \
&& composer require aws/aws-sdk.php
Image Tags
The image tags are organized by version and what is included in the image. There are 4 different tag variants for each published version: mysqli, mysqli-browscap, mysqli-lite_php_browscap, and mysqli-full_php_browscap. For the browscap variants, the updated php.ini file to use the browscap file is included.
- mysqli
- This base variant includes MySQLi extension and memcached extension along with Composer. This variant is the best for the majority of applications.
- mysqli-browscap
- The first variant that includes the browscap file. Includes everything in the mysqli variant with the PHP specific browscap file.
- mysqli-lite_php_browscap
- Includes the lite, smaller, browscap file.
- mysqli-full_php_browscap
- For those that want the full browscap file. This is the largest browscap file available making this variant the largest. Only use this variant if need the full featured browscap file
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 and memcached extensions are installed into the image a this stage along with Composer.
ARG php_version
FROM php:${php_version}-fpm-alpine
WORKDIR /app/
# Install MySQLi
RUN docker-php-ext-install mysqli
# Install Memcached Extensions
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
# Install PHP Composer
RUN mkdir /composer/ \
&& cd /composer/ \
&& php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
&& php composer-setup.php \
&& php -r "unlink('composer-setup.php');" \
&& mv composer.phar /usr/local/bin/composer
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.
ARG php_version
FROM djpic/php:${php_version}-mysqli
COPY php.ini /usr/local/etc/php/php.ini
COPY browscap.ini /usr/local/etc/php/browscap.ini
WORKDIR /app/
Below is the updated line in the php.ini file.
[browscap]
browscap = /usr/local/etc/php/browscap.ini
The build.sh bash script is the second to last piece of the puzzle. Using the passed command line argument for the version, the shell script begins to build the MySQLi image first. From there, the script continues 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. The variants are then pushed to DockerHub.
#!/bin/bash
# Pull the command line argument included when the script is executed
php_version=$1
# Confirm the php_version is defined
if [[ -v php_version ]]; then
# Build php image with mysqli and memcached extension
cd MySQLi
docker build --build-arg php_version=$php_version --tag djpic/php:$php_version-mysqli --tag $CI_REGISTRY_IMAGE/php:$php_version-mysqli .
# Build image with browscap file
cd browscap
wget -O browscap.ini https://browscap.org/stream?q=PHP_BrowsCapINI
docker build --build-arg php_version=$php_version --tag djpic/php:$php_version-mysqli-browscap --tag $CI_REGISTRY_IMAGE/php:$php_version-mysqli-browscap .
rm browscap.ini
wget -O browscap.ini https://browscap.org/stream?q=Full_PHP_BrowsCapINI
docker build --build-arg php_version=$php_version --tag djpic/php:$php_version-mysqli-full_php_browscap --tag $CI_REGISTRY_IMAGE/php:$php_version-mysqli-full_php_browscap .
rm browscap.ini
wget -O browscap.ini https://browscap.org/stream?q=Lite_PHP_BrowsCapINI
docker build --build-arg php_version=$php_version --tag djpic/php:$php_version-mysqli-lite_php_browscap --tag $CI_REGISTRY_IMAGE/php:$php_version-mysqli-lite_php_browscap .
rm browscap.ini
# Docker Push Image
docker push djpic/php:$php_version-mysqli
docker push djpic/php:$php_version-mysqli-browscap
docker push djpic/php:$php_version-mysqli-full_php_browscap
docker push djpic/php:$php_version-mysqli-lite_php_browscap
docker push $CI_REGISTRY_IMAGE/php:$php_version-mysqli
docker push $CI_REGISTRY_IMAGE/php:$php_version-mysqli-browscap
docker push $CI_REGISTRY_IMAGE/php:$php_version-mysqli-full_php_browscap
docker push $CI_REGISTRY_IMAGE/php:$php_version-mysqli-lite_php_browscap
cd ../../
else
# PHP Version missing, error out script with exit code 1
echo "PHP Version Missing"
exit 1
fi
Finally comes the glue that brings everything together: .gitlab-ci.yml. Each PHP version gets its own job allowing the jobs to run in parallel vs. in series, reducing the total build time. Each job executes in a docker-in-docker (DinD) container making cleanup easy.
The jobs are straightforward and follow the same pattern. First using before_script, bash is installed into the deployed docker container followed by the registry login. In this scenario, the images are uploaded to both DockerHub and GitLab container registries. The `build.sh` script is granted the necessary permissions and executed with the version to be built provided as a command-line argument.
The GitLab pipeline is scheduled to run once a week to keep the containers up-to-date with the latest bug fixes.
# Gitlab CI to build docker images
stages:
- build
- release
services:
- docker:dind
# Build out the PHP version 8.1
php-8.1:
stage: build
tags:
- DockerExe
before_script:
- apk update
- apk add bash
- docker login --username $dockerhub_username --password $dockerhub_password
- docker login --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- chmod 755 build.sh
- ./build.sh 8.1
# Build out the PHP version 8.2
php-8.2:
stage: build
tags:
- DockerExe
before_script:
- apk update
- apk add bash
- docker login --username $dockerhub_username --password $dockerhub_password
- docker login --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- chmod 755 build.sh
- ./build.sh 8.2
# Build out the PHP version 8.3
php-8.3:
stage: build
tags:
- DockerExe
before_script:
- apk update
- apk add bash
- docker login --username $dockerhub_username --password $dockerhub_password
- docker login --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- chmod 755 build.sh
- ./build.sh 8.3
# Build out the PHP version 8.4
php-8.4:
stage: build
tags:
- DockerExe
before_script:
- apk update
- apk add bash
- docker login --username $dockerhub_username --password $dockerhub_password
- docker login --username $CI_REGISTRY_USER --password $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- chmod 755 build.sh
- ./build.sh 8.4
include:
# Only Run the production CI when a production tag is added.
- local: .release.gitlab-ci.yml
rules:
- if: '$CI_COMMIT_TAG =~ /^prod-.*$/'
when: always
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.
project_name: "samplecomposefile"
services:
nginx:
image: djpic/nginx:phpfpm
networks:
- Traefik
- 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"
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:
Traefik:
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. Remember to update the image names.
This image has proved invaluable to my development. I use this image 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.
Follow my Twitter account @djpic_llc for updates to this article and other announcements. I also welcome all constructive inputs. If anything is incorrect or needs further explanation, please do contact me.