Photo by CHUTTERSNAP on Unsplash

In the previous tutorial we explored the basics of Docker by running a simple PHP website in a container that has a built-in web server. But what if we want to set up a database?

Well we can simply install a database engine (eg MySQL) to our image and recreate the container, right?

Technically, you can do that, but it defeats the purpose of Docker. Docker is different to virtual machines and it is designed to be highly scalable and isolable.

The Docker way to do things is to create a new image + containers for the database! Furthermore, we can even extract the web server from our PHP container onto its own, and end up having 3 containers with different services on them, eg PHP, Apache and MySQL. This concept of splitting components in a system into smaller chunks is known as microservices.

Now at this point, you may ask:

  1. So containers are like tiny virtual machines, if we have multiple containers, how do they talk to each other?
  2. If we are going to create all the containers using the CLI, isn’t that tedious? There are a lot of typings to do, is there a better way?

To answer these questions, we present: Docker Compose. Docker compose helps to organise our images and containers in one YAML config file.

Instead of typing this:

docker run -v /super/long/path/to/volume/folder:/var/www/html php-appdocker run -p 3306:3306 mysqldocker run -p 80:80 nginx

We can just type:

docker-compose up -d php-app nginx mysql

php-app, nginx and mysql are services defined in your docker-compose.yml file, which is located in your project folder. The -d flag is just telling docker to run those containers as daemon, ie running in the background.

Let’s take a look at some of the common options in docker-compose.yaml.

Note: we can place a .env file on the same directory as docker-compose.yml and access the environmental variable using ${} in docker-compose.yml .

Here is my directory structure:

The Docker Compose file


docker-compose.yml always start with a version key. This tell docker compose to read this file in the specified version syntax. Each version has its own options and syntax, here is the reference.


All the services created by docker are isolated by default. That means they can’t talk to each other unless they are living under the same network. The networks key here defines a list of networks that will be used by the services defined in this docker-compose.yml . To place a service under a network, we just need to assign the network name under the networks option in the service (refer to example below).

Once we place both services under the same network, they can refer to each other using their service name as the IP address. Eg. php-app can talk to mysql via mysql:3306 instead of something like Note: mysql must expose the port 3306 .


This is where we define our services.

Building the services

Once we are done with the docker compose file, run the following to build the docker images for the services.

# in the directory containing the docker-compose.ymldocker-compose build php-app

This will build our php-app service. We don’t need to build mysql since we don’t have a Dockerfile and it is based on a pre-built docker image.

Running and stopping the services

# starting the services
docker-compose up -d php-app mysql
#stopping the services
docker-compose stop
# To stop php-app only
docker-compose stop php-app

Executing one off command in containers

# this will run the 'ls' command in php-app
docker-compose exec php-app ls

List all containers

docker-compose ps

In Summary

We created 2 services in docker-compose.yml, and they are able to communicate with each other via the backend network. Unlike the traditional way to deploy app where all the components of an app locate under 1 environment, docker allows us to extract the services out into smaller, more-manageable containers, otherwise known as microservices.

With the microservice architecture, its modularity allows us to quickly experiment with technology and scale up/down app components as needed without affecting the other part of the app. For example, if our site’s traffic goes up, we can simply add a caching service to our service stack or increase the number of containers that is serving our site.

The microservice architecture may have minimal impact on smaller apps, but medium or large enterprise apps would appreciate it more than anyone else.

Web Development. . Follow me on Youtube: