Set up PHP Development Environment with Docker

This article shows how you can set up a PHP development environment using Docker. We will be installing PHP, MySQL & Nginx, and PHPMyAdmin in the docker containers.

Here’s a basic introduction to docker and docker tools & terminologies: Introduction to Docker and its Tools & Terminologies.

Pre-requisites

Docker should be installed on your machine: https://docs.docker.com/engine/install/

Instead of using the docker command line tool to run the services, it’s better to use Docker Compose which lets you specify all the required services in the YAML file.

Add Nginx Service

Create a docker-compose.yaml file inside your project folder.

docker-compose.yaml


version: '3'
services:
    web:
        image: nginx:latest
        ports:
            - "80:80"
  • version = YAML configuration version. The latest version is 3. Version is deprecated.
  • web = Name given to the server. We can give any name.
  • image = Image to download and use.
    ImageName:Version
    If we specify “latest”, then the latest version of the image will be downloaded.
    https://hub.docker.com/_/nginx/
    We can also specify a specific version of the image, like:
    nginx:1.22.1
  • ports = port forwarding
    local-machine-port:docker-image-port
    If we specify 80:80 in the YAML file, then it will forward port 80 of the local machine to port 80 of the docker image.
    Any request on the host machine to http://127.0.0.1 will be forwarded to the NGINX server running in the container.

Create and start nginx container

The following command will pull the specified service image from the repository and start the containers.

Create and start docker containers in the foreground


docker-compose up

Create and start docker containers in the background
with -d option


docker-compose up -d

Check docker-compose logs


docker-compose logs

You should be able to browse http://localhost orhttp://127.0.0.1.

Add volume to the nginx container

  • Volumes are persistent data used and created by docker containers.
  • With the use of volumes, any changes made to the files in the volumes are immediately changed in the container.
  • The files are shared between the host and the container.

We will add the following:

  • Nginx configuration file nginx.conf
  • app folder where our project’s source code will reside.

docker-compose.yaml


version: '3'
services:
    web:
        image: nginx:latest
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
            - ./app:/app
  • The default configuration file path for Nginx is /etc/nginx/conf.d/nginx.conf.
  • The nginx.conf file from the host is placed at /etc/nginx/conf.d/nginx.conf inside the container.
  • We will keep our project files (HTML, PHP, Javascript, CSS) in the app folder in our project root.
  • The /app folder is also created inside the container.

nginx.conf


server {
    listen 80 default_server;
    root /app/public;
} 

This tells Nginx to serve files from the directory /app/public.

app/public/index.html

Create a file app/public/index.html


<h1>Hello, World!</h1>

Stop & Start the container


docker-compose stop
docker-compose up -d

When you browse http://127.0.0.1, you should now see the “Hello World” heading text.

Add PHP Service

To run PHP scripts/files, we need to add the PHP service container.

docker-compose.yaml


version: '3'
services:
  web:
    image: nginx:latest
    ports:
      - '80:80'
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
      - ./app:/app
  php:
    image: php:fpm
    volumes:
      - ./app:/app
  • We are using php-fpm image. https://hub.docker.com/_/php/
  • FPM = FastCGI Process Manager
  • php-fpm will download the latest PHP image.
  • But, we can specify the version number as well, like php:8.0-fpm, php:7.4-fpm, etc.

PHP service needs access to .php files from the /app directory of the container. Hence, we need to mount /app volume in the PHP service as well in the docker-compose.yaml file.

The app folder is now accessible:

  • in the host machine
  • in the nginx container
  • in the php container

nginx.conf

We also need to configure the Nginx server to make it able to run .php files.


server {
    listen 80 default_server;
    root /app/public;

    index index.php index.html index.htm;

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;     
    }
} 
  • The index line tells the server to look for index.php instead of index.html as the default page.
  • The location block instructs NGINX to run any file with a .php extension through the PHP service (fastcgi_pass php:9000, where php is the name of the service configured in docker-compose.yml).

app/public/index.php

Create a phpinfo file at app/public/index.php:


<?php
phpinfo();

Stop & Start the container


docker-compose stop
docker-compose up -d

When you browse http://127.0.0.1, you can see the phpinfo() output.

To interact PHP with MySQL database server, we need to install PDO_MySQL Driver.

php-fpm is our base PHP image.

We will install the PDO package into the php-fpm image. We need to build our own image using the php-fpm image as the base image. We need to update the docker-compose.yaml file.

docker-compose.yaml


version: '3'
services:
    web:
        image: nginx:latest
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
            - ./app:/app
    php:
        build:
            context: .
            dockerfile: PHP.Dockerfile
        volumes:
            - ./app:/app

We also need to create a Dockerfile that contains instructions on installing the pdo & pdo_mysql packages. We will also be including the instruction to install Xdebug.

PHP.Dockerfile


FROM php:fpm

RUN docker-php-ext-install pdo pdo_mysql

# If you need to install mysqli instead of pdo
# RUN docker-php-ext-install mysqli

RUN pecl install xdebug && docker-php-ext-enable xdebug

Stop, Build & Start the container


docker-compose stop
docker-compose up -d

When you browse http://127.0.0.1, you can see the phpinfo() output. In the phpinfo() output, you should be able to see the PDO_MySQL & XDEBUG extensions installed.

Add MySQL Service

docker-compose.yaml

In the below YAML file, we can see that the MySQL data (tables, records, etc.) are stored in a docker volume named mysqldata.


version: '3'
services:
    web:
        image: nginx:latest
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
            - ./app:/app
    php:
        build:
            context: .
            dockerfile: PHP.Dockerfile
        volumes:
            - ./app:/app
    mysql:
        image: mariadb:latest
        environment:
            MYSQL_ROOT_PASSWORD: 'secret'
            MYSQL_USER: 'mukesh'
            MYSQL_PASSWORD: 'chapagain'
            MYSQL_DATABASE: 'blog'
        volumes:
            - mysqldata:/var/lib/mysql
        ports:
            - 3306:3306
volumes:
    mysqldata: {}
  • image: mariadb:latest will download the latest mariadb image version. We can also specify the version number like mariadb:10.8. https://hub.docker.com/_/mariadb/
  • environment: MySQL root username = root. We have set the root password as secret. Also, created a new MySQL user mukesh & a database blog.
  • volumes: Docker volume named mysqldata is created and the MySQL tables and records data are stored in that docker volume.
  • ports: Port forwarding. Request to port 3306 of the local/host machine is forwarded to port 3306 of the docker image container.

Stop & Start the container


docker-compose stop
docker-compose up -d

Check docker volume


docker volume ls

DRIVER    VOLUME NAME
local     docker-project_mysqldata

Inspect the docker volume


docker volume inspect docker-project_mysqldata

[
    {
        "CreatedAt": "2022-10-29T19:05:40Z",
        "Driver": "local",
        "Labels": {
            "com.docker.compose.project": "docker-project",
            "com.docker.compose.version": "1.29.1",
            "com.docker.compose.volume": "mysqldata"
        },
        "Mountpoint": "/var/lib/docker/volumes/docker-project_mysqldata/_data",
        "Name": "docker-project_mysqldata",
        "Options": null,
        "Scope": "local"
    }
]

If you need to store the MySQL data in the host machine instead, then you can use the following YAML configuration:

docker-compose.yaml


version: '3'
services:
    web:
        image: nginx:latest
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
            - ./app:/app
    php:
        build:
            context: .
            dockerfile: PHP.Dockerfile
        volumes:
            - ./app:/app
    mysql:
        image: mariadb:latest
        environment:
            MYSQL_ROOT_PASSWORD: 'secret'
            MYSQL_USER: 'mukesh'
            MYSQL_PASSWORD: 'chapagain'
            MYSQL_DATABASE: 'blog'
        volumes:
            - ./data/db:/var/lib/mysql
        ports:
            - 3306:3306

This will create a data/db folder in your host machine project folder and the MySQL data are saved into that folder.

Note:

  • It’s better to use docker volume to store the MySQL data instead of storing it in the local filesystem of the host machine.
  • If data is stored in the host machine, and if the application is uploaded to an online server then the local data will also be uploaded there.
  • So, to avoid this type of scenario, it’s better to store data in the docker volume.

Stop & Start the container


docker-compose stop
docker-compose up -d

app/public/db.php

PHP code to connect MySQL using PDO


<?php
$dbHost = 'mysql';
$dbName = 'blog';
$dbUser = 'mukesh';
$dbPassword = 'chapagain';

try {
    $pdo = new PDO(
        "mysql:dbname={$dbName};host={$dbHost}",
        $dbUser,
        $dbPassword,
        [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
    );

    $query = $pdo->query('SHOW VARIABLES like "version"');
    $row = $query->fetch();
    echo 'MySQL version:' . $row['Value'];
} catch (PDOException $e) {
    echo $e->getMessage();
}

You can browse http://127.0.0.1/db.php and it will show the Mariadb version number as output.

Add Database Management Tool

Add PhpMyAdmin

phpMyAdmin is a free software tool written in PHP, intended to handle the administration of MySQL over the Web. phpMyAdmin supports a wide range of operations (managing databases, tables, columns, relations, indexes, users, permissions, etc) on MySQL and MariaDB.

docker-compose.yaml


version: '3'
services:
    web:
        image: nginx:latest
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
            - ./app:/app
    php:
        build:
            context: .
            dockerfile: PHP.Dockerfile
        volumes:
            - ./app:/app
    mysql:
        image: mariadb:latest
        environment:
            MYSQL_ROOT_PASSWORD: 'secret'
            MYSQL_USER: 'mukesh'
            MYSQL_PASSWORD: 'chapagain'
            MYSQL_DATABASE: 'blog'
        volumes:
            - ./data/db:/var/lib/mysql
        ports:
            - 3306:3306
    phpmyadmin:
        image: phpmyadmin
        restart: always
        ports:
            - '8080:8080'
        environment:
            - PMA_ARBITRARY=1

Stop & Start the container


docker-compose stop
docker-compose up -d

PhpMyAdmin can be browsed with the URL http://127.0.0.1:8080.

Add Adminer

Alternative to PhpMyAdmin

Adminer (formerly phpMinAdmin) is a full-featured database management tool written in PHP. Conversely to phpMyAdmin, it consist of a single file ready to deploy to the target server. Adminer is available for MySQL, PostgreSQL, SQLite, MS SQL, Oracle, Firebird, SimpleDB, Elasticsearch and MongoDB.

docker-compose.yaml


version: '3'
services:
    web:
        image: nginx:latest
        ports:
            - "80:80"
        volumes:
            - ./nginx.conf:/etc/nginx/conf.d/nginx.conf
            - ./app:/app
    php:
        build:
            context: .
            dockerfile: PHP.Dockerfile
        volumes:
            - ./app:/app
    mysql:
        image: mariadb:latest
        environment:
            MYSQL_ROOT_PASSWORD: 'secret'
            MYSQL_USER: 'mukesh'
            MYSQL_PASSWORD: 'chapagain'
            MYSQL_DATABASE: 'blog'
        volumes:
            - ./data/db:/var/lib/mysql
        ports:
            - 3306:3306
    db-admin:
        image: adminer:latest
        ports:
            - '8080:8080'

Stop & Start the container


docker-compose stop
docker-compose up -d

Adminer can be browsed with the URL http://127.0.0.1:8080.

List all active (running) containers


docker ps

CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                                            NAMES
312e0e788e0a   phpmyadmin             "/docker-entrypoint.…"   8 seconds ago   Up 6 seconds   80/tcp, 0.0.0.0:8080->8080/tcp                   php-mysql-docker_phpmyadmin_1
8a17f2861350   mariadb:latest         "docker-entrypoint.s…"   8 seconds ago   Up 6 seconds   0.0.0.0:3306->3306/tcp                           php-mysql-docker_mysql_1
4cf344ff3d40   php-mysql-docker_php   "docker-php-entrypoi…"   8 seconds ago   Up 6 seconds   9000/tcp                                         php-mysql-docker_php_1
7913a5305d39   nginx:latest           "/docker-entrypoint.…"   8 seconds ago   Up 6 seconds   0.0.0.0:80->80/tcp                               php-mysql-docker_web_1

Download source code:
https://github.com/chapagain/php-mysql-docker/tree/1.0.0

Reference:
https://www.sitepoint.com/docker-php-development-environment/

Hope this helps. Thanks.