Web Development with Docker, from PHP7.x to PHP8

Written by Vincent Bruijn

My other article about web development via the MAMP stack in Docker is the most popular article of this website to date. That’s great! It feels good to give back to the community from which I learned so much over tha past 15 years! This article is a follow up when using a custom PHP8 Docker image.

Extend PHP8 for Docker MAMP development

In the other article I describe a way to use Docker’s docker-compose.yml file to easily get a MAMP stack started, even with phpmyadmin! Lately I wanted to move to PHP8, since I want to keep on track with latest features and so I searched Docker hub for an image containing PHP8 and Apache. The image I found was PHP’s default for version 8 so I updated my docker-compose.yml file to install it.

This would of course be a test to prove the flexibility of Docker: you drop one image containing a certain version, in favour of a new version and off you go! This turned out a little harder than expected.

The hassle had nothing to do with Docker itself, as changing the docker-compose.yml file and rebuild the images was sufficient to get it all running again. But soon I discovered that the image of PHP8 lacked certain extensions I needed: I was unable to connect to MySQL via mysqli and I could not modify images with the gd extension.

I decided to write a custom PHP.Dockerfile in order to have the default image extended with what I needed, and then update the docker-compose.yml file to let it be used in my MAMP setup.

FROM php:8.0-apache

ENV PHP_EXTRA_CONFIGURE_ARGS="--with-apxs2 --disable-cgi --with-zip"

RUN apt-get update && apt-get install -y \
       libfreetype6-dev \
       libjpeg62-turbo-dev \
       libpng-dev \
       libicu-dev \
       libzip-dev \
       && a2enmod rewrite \
       && docker-php-ext-install mysqli \
       && docker-php-ext-install exif \
       && docker-php-ext-install bcmath \
       && docker-php-ext-install intl \
       && docker-php-ext-configure gd --with-freetype --with-jpeg \
       && docker-php-ext-install gd \
       && docker-php-ext-install zip \
       && chsh -s /bin/bash www-data \
       && rm -rf /var/lib/apt/lists/*

CMD ["apache2-foreground"]

So in short, the above Dockerfile will grab the image named 8.0-apache from PHP’s Docker hub account page, will add some additional arguments. Then apt is used to add additional dependencies. First a set of dependencies for PHP extensions, then Apache’s mod_rewrite as I want to use it in my Apache configuration, followed by php extensions I want to add. So far so good; the image is nicely built.

But now for the docker-compose.yml file. How do I need to modify it to let it use a local Dockerfile instead of retrieving something from the hub? It was a bit of a puzzle, and I made few mistakes in the depth of properties, but in the end the following did the trick: define a build property with two childeren being context and dockerfile. These two together tell docker-compose to look in the current directory for the desired Dockerfile, in this case PHP.Dockerfile.

version: '3'

services:
  web:
    build:
      context: .
      dockerfile: ./PHP.Dockerfile
    ports:
      - '80:80'
    volumes:
      - /Users/vincentb/Sites/_docker/apache:/etc/apache2/sites-enabled
      - /Users/vincentb/Sites:/var/www/html
      - /Users/vincentb/Sites/_docker/php/php.ini:/usr/local/etc/php/php.ini
    depends_on:
      - mariadb
    extra_hosts:
      - 'ax710.test:127.0.0.1'
      # - 'some-other-host.test:127.0.0.1'
    networks:
      - dev-env
  phpmyadmin:
    image: phpmyadmin:latest
    ports:
      - 8080:80
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=mariadb
    depends_on:
      - mariadb
    volumes:
      - /Users/vincentb/Sites/_docker/php/php.ini:/usr/local/etc/php/php.ini
    networks:
      - dev-env
  mariadb:
    image: mariadb:latest
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 'secret'
      MYSQL_USER: 'vincentb'
      MYSQL_PASSWORD: 'secret'
      MYSQL_DATABASE: 'vincentb'
    volumes:
      - mysqldata:/var/lib/mysql
    ports:
      - 3306:3306
    networks:
      - dev-env
volumes:
  mysqldata: {}
networks:
  dev-env: {}

OK, cool! All set! Now call the following on your command line and the new PHP image should be built and you’re ready to go.

$ docker-compose -f ./docker-compose.yml up -d

So to conclude: Docker has proven to be flexible! My machine is not polluted with all kinds of packages all over the place, but everything lives in a directory from Docker, where you can easily do a cleanup from. Switching versions is easy, though it was a bit of a hassle to configure docker-compose.yml in combination with a local Dockerfile. Since I now know this, I will probably often use this to locally orchestrate a setup of docker-compose.yml with several Dockerfiles.