Set up a development environment with multiple PHP versions using Docker

Since I've moved away from PHP I did not work on it much. Lately, however, I had to touch an old project (using PHP 5.6) and a new one in 7.4.

I got a new Apple laptop (an M1) and I had no intention to spend time installing different versions of PHP, so instead I made a small service using Docker to have a ready environment to work on.

Install Docker

For this step, just go to https://docs.docker.com/get-docker/ and follow the instructions for yours OS.

Let's start

We are going to use docker-compose and, in total, we need few folders and 3 files.
First, create a new folder, that will be your project root:
Inside, create 3 files:
Dockerfile.74: This will be your Dockerfile for PHP 7.4.
Dockerfile.56: This will be your Dockerfile for PHP 5.6.
docker-compose.yml: This will contain our environment configuration.

Docker.74

The content of this file should be like this:

FROM php:7.4-apache
RUN docker-php-ext-install mysqli
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

So what we do here is this: FROM php:7.4-apache will create the container for PHP 7.4, using apache. On next line RUN docker-php-ext-install mysqli will add extensions to PHP, in this case we add mysqli for a later connection to a database.
You can add what you need for your project, for example, RUN docker-php-ext-install mysqli pdo zip to have PDO and ZIP extension activated as well.
Lastly, RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini copies the production version of php.in to be used by the container. You can remove this line if you prefer the standard PHP configuration.

Docker.56

This is basically identical to the Docker.74, only changing the container.

FROM php:5.6-apache
RUN docker-php-ext-install mysqli
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"

docker-compose.yml

Now let's write our docker-compose.yml file. Starting from the first service, add

version: '3.8'

services:
  php74:
    build:
      context: .
      dockerfile: Dockerfile.74
    ports:
      - 80:80
    volumes:
      - ./php74:/var/www/html/
      

Here we name our service php74 and connect it to our Dockerfile.74 file. Next, we map to port 80 and add a volume to store our files. Create a new folder called php74 on your project root, anything in it be loaded on our PHP 7.4 server. inside you can place a test file, info.php, and add the following line inside:

<?php phpinfo(); ?>

At this point, we can do a quick test. On terminal, cd to the project folder and run

docker-compose up

Then on your browser go to http://localhost:80/info.php. The PHP info page should load and confirming you are running version 7.4.

All right, stop the process and lets continue. Next we add the PHP 5.6 server, same under services:

  php56:
    build:
      context: .
      dockerfile: Dockerfile.56
    ports:
      - 88:80
    volumes:
      - ./php56:/var/www/html/

Same as before, create a folder called a php56 and inside put a copy of the info.php file. This time we map to port 88, so if we want to run for a test it again, the URL for our PHP 5.6 server will be http://localhost:88/.

Expanding our server

Now let's add a database for our project, here we use mysql:

  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    volumes:
      - ./mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password #example of a password

Here we use directly an image instead of a Dockerfile. We add a restart: always to make it restart if it stops and then we set volumes.
Let's create a new folder mysql in our project root. This will prevent our databases from being destroyed when the server stop. Then we map it to /var/lib/mysql directory on our db container.
Finally, we add a password with an environment variable; Not much security needed here so we use the word password. You can change it.

If you need to edit your mysql configuration, you can append another volume and connect it to a file.

      - ./configs/my.cnf:/etc/my.cnf

You can do the same for your PHP configuration, if needed.


For Mac M1

mysql image needs the following line added to the configuration if you have a Mac with M1 processor like me:

    platform: linux/x86_64

Quick DB edit

If we need something to edit/view our database, we can add phpmyadmin.
So we add a new service:

  phpmyadmin:
    image: phpmyadmin
    restart: always
    ports:
      - 8080:80

We map it to port 8080. All done.

Now your docker-compose.yml file should look like this:

version: '3.8'

services:
  php74:
    build:
      context: .
      dockerfile: Dockerfile.74
    ports:
      - 80:80
    volumes:
      - ./php74:/var/www/html/

  php56:
    build:
      context: .
      dockerfile: Dockerfile.56
    ports:
      - 88:80
    volumes:
      - ./php56:/var/www/html/

  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    volumes:
      - ./volumes/mysql:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: password #example of a password

  phpmyadmin:
    image: phpmyadmin
    restart: always
    ports:
      - 8080:80

We can finally run our environment. Let's use docker-compose up again and we can access the following domains:
http://localhost:80/ : Our PHP 7.4 domain.
http://localhost:88/ : Our PHP 5.6 domain.
http://localhost:8080/ : Our PHPMyAdmin domain.

To connect to the database from any of your apps, just use the name db (the database service name) as host and root as user. The password will be what is added on MYSQL_ROOT_PASSWORD line.

That's it!. Now you can enjoy a nice multi-version PHP development environment for your projects always ready when you need it.