Running a PHP application in Docker

I recently went through the process of converting one of my personal PHP projects to run in Docker. The project itself was originally intended to be as simple as possible, it’s a Blog CMS that I started as a “Vulnerable Web App” similar to DVWA to highlight vulnerable coding practices that may leave an application open to attack.

Where DWVA is built to be exploited and shows instructions on how to compromise its features, I wanted SuperBlog to be more like a realistic application where it wasn’t vulnerable on purpose, but it was vulnerable nonetheless. Things like using proper up-to-date coding practices but missing critical things like input sanitisation.

Anyway, this post isn’t meant to be about SuperBlog or vulnerable Web Apps. I didn’t start the SuperBlog project using Docker initially, it was actually built running in XAMPP, so there were a few things I needed to change to get it working in Docker. That’s what this post is about.

So assuming one has Docker installed, I’m using Windows 10 with Docker Desktop, I’ll start with a Docker-compose file. Docker-compose lets you run multiple Docker containers in a single project. For example, in SuperBlog I have a PHP container and a MySQL container, docker-compose will allow me to define both of those containers and make it easy for those containers to interact.

version: "3.1"

services:
  php:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 80:80
    volumes:
      - ./superblog:/var/www/html/

  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: superblog
      MYSQL_DATABASE: superblog
      MYSQL_USER: superblog
      MYSQL_PASSWORD: superblog
    volumes:
      - mysql-data:/var/lib/mysql

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080

volumes:
  mysql-data:

This is the docker-compose.yml file I’ve defined. You can see I’ve got 3 services, the first service is for PHP, the second is for MySQL and the third is for Adminer to make managing the Database easier. Also note the volumes section at the bottom, this makes the persistent Database volume available to the other services, so that PHP and Adminer can access the Database container.

From there I needed to define a Dockerfile for the PHP container. I didn’t realise this at first, I kept trying to run the application and PHP didn’t know what MySQLi was. The docker-compose file will use the Dockerfile to setup the PHP container. This one is pretty simple:

FROM php:7.4-apache
RUN docker-php-ext-install mysqli

This just defines the PHP image to base my container from and the RUN line ensures the mysqli extensions are installed in the container so that PHP can access the MySQL database.

There were a few places I needed to change the code as well. For example, in the PHP classes that access the database I initially used the defines that were setup in config.php for the database connection. This didn’t work because the PHP code didn’t know where the MySQL container was. I had to update the database HOST to reference the ‘db’ container, leaving the other constants the same.

public function __construct()
{
    $this->conn = new \mysqli('db', DB_USER, DB_PASS, DB_NAME);

    if ($this->conn->connect_error) {
        die("Connection failed: " . $this->conn->connect_error);
    }
}

That was it really. Once the containers were configured and the code was updated in a few places, Docker worked really well. Now instead of having to run the App by starting XAMPP, I can run the command:

$ docker-compose up -d

Which will start all of the containers defined in the docker-compose.yml file.

Please note, SuperBlog is not meant to be deployed anywhere, containerised or not. Please don’t run this application on any production or Web facing server.