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 specify80: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
. or
http://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 ofindex.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
, wherephp
is the name of the service configured indocker-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 likemariadb:10.8
. https://hub.docker.com/_/mariadb/ - environment: MySQL root username =
root
. We have set the root password assecret
. Also, created a new MySQL usermukesh
& a databaseblog
. - 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.