Skip to main content

Docker LAMP Version Sandbox

GitHub Repo

Background

Skip content

This Docker Compose framework is designed to make it simple to test different versions of Apache, PHP, and MySQL. The need arose from a client request to upgrade Drupal from version 8 to version 10 which involved a PHP upgrade from 7 to 8, a MySQL upgrade from 5.7 to 8, and an Ubuntu server upgrade from 18.04 to 22.04. I wanted to be able to easily switch between environments and not deal with the hassle of configuring a handful of different sites in MAMP/WAMP.

As someone who has worked on numerous projects in various stages of development, I've come across the issue of software version compatibility time and again. Specifically when it comes to PHP, I originally administered sites backed by Apache with mod_php and eventually I moved to a php_fpm setup with Apache. I quickly realized this allowed me to run multiple versions of PHP with a single version of Apache as long as I was specifying the correct PHP worker in my virtual host config. Eventually I migrated to nginx with php_fpm and adopted a variety of strategies, mostly leveraging ansible, to manage the servers under my purview.

Admittedly I've been a slow adopter to containers and I've often felt more comfortable with a simple VM deployment and apt installations of nginx, php, mysql, postgres, etc. I mean, I was already virtualizing in the cloud so why do I need to virtualize yet again with containers?

The reason smacked me directly in the face when I was simultaneously trying to tweak PHP versions along with figuring out how I was going to run two instances of MySQL using MAMP Pro. Oh the humanity... Fortunately a toilet TV session stumbled me upon a forum post where someone was experiencing the same issue and the top comment pointed to using containers. The lightbulb finally illuminated in my head and I immediately began dockerizing my configuration.

Using Containers in this Repo

The compose.yml file instructs Docker to spin up a collection of containers for Apache with PHP, MySQL, and PHPMyAdmin tools. An Nginx load balancer container is also configured which is reponsible for routing traffic between the latest and legacy containers over ports 8080 and 8081 respectively.

The following containers are created:

  1. Nginx Load Balancer - Latest Version
  2. Apache PHP - Latest Version
  3. Apache PHP - v7.4.33
  4. MySQL - Latest Version
  5. PHPMyAdmin for MySQL - Latest Version
  6. MySQL - v5.7.43
  7. PHPMyAdmin for MySQL - v5.7.43

The framework is designed to be platform agnostic and has been tested to work on MacOS, Windows, and Linux. Windows does use WSL2 to host Docker, but I've grown more fond of WSL2 and I can tolerate it as a dependency for Windows developers.

Starting containers

docker compose up -d

[+] Running 8/8
✔ Network docker-lamp-version-sandbox_vpc_01 Created 0.0s
✔ Container docker-lamp-version-sandbox-db-1 Started 0.0s
✔ Container docker-lamp-version-sandbox-db_legacy-1 Started 0.0s
✔ Container docker-lamp-version-sandbox-db_legacy_phpmyadmin-1 Started 0.1s
✔ Container docker-lamp-version-sandbox-web_legacy-1 Started 0.1s
✔ Container docker-lamp-version-sandbox-db_phpmyadmin-1 Started 0.1s
✔ Container docker-lamp-version-sandbox-web_latest-1 Started 0.1s
✔ Container docker-lamp-version-sandbox-app_lb1-1 Started 0.0s

Viewing containers

docker ps

IMAGE COMMAND CREATED STATUS PORTS NAMES
nginx-latest "/docker-entrypoint.…" 22 seconds ago Up 19 seconds 80/tcp, 0... docker-lamp-version-sandbox-app_lb1-1
apache-php-latest "docker-php-entrypoi…" 22 seconds ago Up 20 seconds 80/tcp docker-lamp-version-sandbox-web_latest-1
phpmyadmin "/docker-entrypoint.…" 22 seconds ago Up 21 seconds 80/tcp docker-lamp-version-sandbox-db_legacy_phpmyadmin-1
phpmyadmin "/docker-entrypoint.…" 22 seconds ago Up 20 seconds 80/tcp docker-lamp-version-sandbox-db_phpmyadmin-1
apache-php-7.4.33 "docker-php-entrypoi…" 22 seconds ago Up 20 seconds 80/tcp docker-lamp-version-sandbox-web_legacy-1
mysql-5.7 "docker-entrypoint.s…" 22 seconds ago Up 22 seconds 3306/tcp, 3... docker-lamp-version-sandbox-db_legacy-1
mysql-latest "docker-entrypoint.s…" 22 seconds ago Up 21 seconds 3306/tcp, 3... docker-lamp-version-sandbox-db-1

Stopping containers

docker compose down

[+] Running 8/8
✔ Container docker-lamp-version-sandbox-app_lb1-1 Removed 0.5s
✔ Container docker-lamp-version-sandbox-web_latest-1 Removed 1.9s
✔ Container docker-lamp-version-sandbox-web_legacy-1 Removed 1.7s
✔ Container docker-lamp-version-sandbox-db_phpmyadmin-1 Removed 1.8s
✔ Container docker-lamp-version-sandbox-db_legacy_phpmyadmin-1 Removed 2.0s
✔ Container docker-lamp-version-sandbox-db-1 Removed 1.0s
✔ Container docker-lamp-version-sandbox-db_legacy-1 Removed 2.1s
✔ Network docker-lamp-version-sandbox_vpc_01 Removed 0.2s

Resources are made available on localhost over several tcp ports. All traffic is routed through the nginx load balancer and then onward to the upstream container, even MySQL chatter.

The following URLs are created:

PHP source code can be placed into src/ directories within each folder of either the apache-php-latest version or the most recent legacy, apache-php-7.4.33 version.

Database containers can be accessed via the shared docker networking using the name defined in the composer.yml file. For example, using the db_legacy container running MySQL 5.7 can be implemented in a PHP code snippet:

<?php
// MySQL Server Host
// 'db_legacy' is the Docker Compose service name for MySQL
$host = 'db_legacy';

// MySQL Root Password
// Same as MYSQL_ROOT_PASSWORD in docker-compose.yml
$rootPassword = 'mysql-root-sdlc';

// Connect to MySQL server with root credentials
$rootPdo = new PDO("mysql:host=$host", 'root', $rootPassword);

MySQL containers

Since containers are typically ephemeral and can be created and destroyed at will, it's important to store MySQL data in docker volumes for persistence. These volumes are defined in the compose.yml file and will maintain their state between creates and destroys. It's important these files are backed up if you want to keep around your database data.

volumes:
db_data:
db_legacy_data:

Volumes can be listed using the docker cli and are preserved between container creations and destroys.

docker volume ls

local docker-lamp-version-sandbox_db_data
local docker-lamp-version-sandbox_db_legacy_data

MySQL Client

The MySQL containers can be accessed via the Nginx load balancer tcp stream which listens on tcp/3380 and tcp/3381. Each of these ports correspond to either the latest or the legacy container for MySQL. The connection is standard, but does require a few named parameters.

MySQL Client Login Example:

mysql --host=localhost --port=3380 --user=root -p --protocol=tcp
mysql --host=localhost --port=3381 --user=root -p --protocol=tcp
note

The root user password is defined in the compose.yml file.
The password is initially set to mysql-root-sdlc

MySQL Database Import Example:

mysql --host=localhost --port=3381 --user=root -p --protocol=tcp DATABASE_NAME < FILE.sql

MySQL Database Dump Example:

mysqldump --host=localhost --port=3381 --user=root -p --protocol=tcp DATABASE_NAME > FILE.sql

Docker Commands

View images:

docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE
nginx-latest latest a2bc4287970f 3 hours ago 187MB
apache-php-7.4.33 latest 4c2c4df02190 2 days ago 479MB
apache-php-latest latest a93bcf0730b4 3 days ago 531MB
phpmyadmin latest b5821d22d3db 8 days ago 562MB
mysql-latest latest e645c1e8d39e 5 weeks ago 577MB
mysql-5.7 latest 3e51c56d1e0b 6 weeks ago 581MB

Delete specific image:

docker image rm IMAGE_NAME

Testing PHP Web Apps

Place PHP code into the respective apache-php-*/src folder and update any database credentials as needed. Most typical PHP modules are installed by default, but you may need to tweak and add your own by editing the Dockerfile within each version folder. The folder structure is shown below.

PHP 7.4.33

tree apache-php-7.4.33
apache-php-7.4.33
├── Dockerfile
└── src
├── index.php
├── info.php
└── init_db.php

localhost:8081/info.php

php-7.4.33.png

PHP Latest

tree apache-php-latest
apache-php-latest
├── Dockerfile
└── src
├── index.php
├── info.php
└── init_db.php

localhost:8080/info.php

php-8.2.10.png

As shown, the folder structures are identical between versions of apache-php to provide consistency.

init_db.php

This PHP file is intended for demonstration purposes only, serving as a basic example to illustrate database connectivity and user creation in MySQL using PHP's PDO extension. It shows how to connect to a MySQL server, create a new database, and establish a new user with full privileges on that database within the containerized environment.

Points to Note

  1. Security: This is not recommended for production environments. Plain text passwords and important credentials should not be hardcoded. They should be securely managed, preferably through environment variables.

  2. Database Operations: Creating databases and users directly in the code is usually done for demonstration or testing. In production, it's often better to use database migrations or administrative tools to manage databases and users.

  3. Error Handling: The error handling in this script is minimal. In a production environment, you should consider more robust logging and error-handling techniques.

PHPMyAdmin

Two instances of PHPMyAdmin are made avaialble at dedicated URLs. They are used to manage both versions of MySQL and are provided for simplicity in viewing databases and running basic queries. Remember that the MySQL shell can still be accessed and databases can be imported and exported easily from the CLI. You should not be using PHPMyAdmin for importing and exporting data.

MySQL Latest

MySQL 5.7

Summary

I personally found this framework to be pretty useful, particularly during my upgrade from Drupal 8 to Drupal 10. It served as a reliable testing environment and streamlined the entire upgrade process. If you're considering a similar upgrade or initiating a new project, this repository may offer a valuable starting point for you.

Please feel free to open Issues to submit Pull Requests to the GitHub repo.