Building a Complete PHP Stack Using Docker and Docker-Compose
Typical PHP setups consist of three main components: a web server, a relational database system, and the PHP interpreter. In this walkthrough, we will assemble a full PHP environment using Docker. You will learn how to create and manage containers for Nginx (web server), MySQL (database), and PHP.
Creating a Simple PHP App to Read from a Database
We will build a basic web application that retrieves a list of cities from a MySQL database and shows them on a web page. This practical example will demonstrate how to implement a functional PHP-based setup.
Prerequisites for This Tutorial
This guide assumes that Docker-CE is already installed on your system and that you have a basic understanding of Docker.
Setting Up the Project Environment
In real-world scenarios, Docker-based applications often consist of several containers. Managing these manually can quickly become chaotic. Docker Compose simplifies this by allowing control over multiple containers through a YAML configuration file.
Installing Docker Compose
curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
Creating the Project Directory
Start by setting up a main directory for your project files and move into it. This will be your working directory (referred to here as WORKING_DIR):
mkdir ~/docker
cd ~/docker
Now create three subdirectories inside it:
mkdir php nginx app
The php directory will store the custom PHP image, the nginx folder will contain the Nginx configuration, and app will house the application code and config files.
Setting Up the PHP Container
We will configure PHP-FPM to communicate with Nginx. The PHP image will be based on the official lightweight Alpine image. It also requires some additional extensions to interact with the MySQL database. Inside the php
folder, create a file named Dockerfile
and include the following:
FROM php:7.1-fpm-alpine3.4
RUN apk update --no-cache \
&& apk add --no-cache $PHPIZE_DEPS \
&& apk add --no-cache mysql-dev \
&& docker-php-ext-install pdo pdo_mysql
This setup uses Alpine Linux for its minimal footprint, ideal for containerized environments. The command docker-php-ext-install
provided by the PHP image simplifies enabling required extensions.
Building the PHP Docker Image
Build your Docker image from the WORKING_DIR:
docker build -t centron-php php/
Creating the docker-compose.yml File
Docker Compose uses a configuration file—commonly named docker-compose.yml
—to define and control multi-container applications. Create this file inside the app
directory:
touch app/docker-compose.yml
Insert the following content into it:
version: '2'
services:
php:
image: centron-php
volumes:
- ./:/app
working_dir: /app
Understanding the docker-compose Configuration
The first line, version: '2'
, defines the Compose file format. Under services
, we declare all the containers to be created—in this case, only one for PHP.
The service php
refers to the image we just built. The volume mapping:
volumes:
- ./:/app
…binds the current host directory to /app
inside the container. The working_dir
specifies where future container commands will run by default.
Running the Containers
Now navigate to the app
directory and bring up the containers:
cd ~/docker/app
docker-compose up -d
To confirm that the PHP container is running properly, use the following:
docker ps
Running Commands Inside Containers with Docker Compose
From the app
directory, you can execute commands inside any containerized service using the following syntax:
docker-compose exec [service] [command]
For instance, to verify the PHP version inside the container, run:
docker-compose exec php php -v
This will output something like:
PHP 7.1.14 (cli) (built: Feb 7 2018 00:40:45) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
Customizing the Nginx Container
Similar to the PHP setup, we need to configure a custom container for Nginx. This time, we just need to supply a virtual host configuration file. While in WORKING_DIR, create a Dockerfile for the Nginx container:
cd ~/docker
touch nginx/Dockerfile
Insert this content into the Dockerfile:
FROM nginx:1.13.8-alpine
COPY ./default.conf /etc/nginx/conf.d/default.conf
This setup uses the lightweight Alpine-based Nginx image and copies a configuration file into place. Create the configuration file:
touch nginx/default.conf
Paste the following into default.conf
:
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /app;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
The line fastcgi_pass php:9000;
links to the PHP container by service name. Docker Compose creates an internal network where each service is accessible by its name.
Now build the Nginx container image:
docker build -t centron-nginx nginx/
Expanding the docker-compose.yml File
Update the app/docker-compose.yml
file to include the web server:
version: '2'
services:
php:
image: centron-php
volumes:
- ./:/app
working_dir: /app
web:
image: centron-nginx
volumes:
- ./:/app
depends_on:
- php
ports:
- 80:80
The depends_on
ensures the PHP container starts first. ports
exposes container port 80 to the host.
Create a simple PHP file at app/index.php
:
<?php phpinfo();
Ensure your firewall allows port 80. Start the containers:
cd ~/docker/app
docker-compose up -d
docker ps
Open your browser and navigate to your server’s IP address. Find it by running:
hostname -I
Setting Up the MySQL Container
The MySQL image can be configured using environment variables. Add a new mysql
service in docker-compose.yml
:
version: '2'
services:
php:
image: centron-php
volumes:
- ./:/app
working_dir: /app
web:
image: centron-nginx
volumes:
- ./:/app
depends_on:
- php
ports:
- 80:80
mysql:
image: mysql:5.7.21
volumes:
- ./:/app
- dbdata:/var/lib/mysql
environment:
- MYSQL_DATABASE=world
- MYSQL_ROOT_PASSWORD=root
working_dir: /app
volumes:
dbdata:
This configuration uses dbdata
to persist MySQL data. Docker manages this named volume to retain your database even if the container is destroyed.
Importing the Sample Database
Download and extract the sample world
database from the official MySQL resources:
curl -L http://downloads.mysql.com/docs/world.sql.gz -o world.sql.gz
gunzip world.sql.gz
Now start the containers:
docker-compose up -d
docker ps
Import the database into MySQL:
docker-compose exec -T mysql mysql -uroot -proot world < world.sql
Access the MySQL CLI and confirm the data import:
docker-compose exec mysql mysql -uroot -proot world
Inside MySQL, run:
select * from city limit 10;
You’ll see a sample list of cities from the database. To exit MySQL:
exit
Developing the Sample PHP Application
With all required containers now operational, we can shift focus to developing our demo application. Update the app/index.php
file with the following content:
<?php
$pdo = new PDO('mysql:host=mysql;dbname=world;charset=utf8', 'root', 'root');
$stmt = $pdo->prepare("
select city.Name, city.District, country.Name as Country, city.Population
from city
left join country on city.CountryCode = country.Code
order by Population desc
limit 10
");
$stmt->execute();
$cities = $stmt->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>centron Rocks!</title>
</head>
<body>
<h2>Most Populous Cities In The World</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Country</th>
<th>District</th>
<th>Population</th>
</tr>
</thead>
<tbody>
<?php foreach($cities as $city): ?>
<tr>
<td><?=$city['Name']?></td>
<td><?=$city['Country']?></td>
<td><?=$city['District']?></td>
<td><?=number_format($city['Population'], 0)?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
</body>
</html>
Open a browser and go to your server’s IP address ([centron-instance-ip]
). You’ll see a table displaying the ten largest cities in the world by population. Well done — you’ve successfully deployed a complete PHP application using Docker!
Final Thoughts
In this guide, you’ve learned how to build and configure a working PHP application environment using Docker. You created custom container images for PHP and Nginx and managed them using Docker Compose. Although simple, this layout closely resembles production-ready Docker environments.
All container images were built and tagged locally. To make your setup more scalable, consider pushing your images to a Docker registry. You can use Docker Hub or create a private registry, which allows for image sharing across multiple hosts.
If you want to expand this setup to support other frameworks or features, you may need to install additional PHP extensions. You can modify the Dockerfile
accordingly. Some extensions require extra system libraries to function correctly, so be sure to check the official PHP documentation to understand their dependencies.
For a deeper dive into Docker Compose and advanced features, consult the official documentation.