Getting started with Docker can be a bit overwhelming. There are images, containers, Dockerfiles, docker-compose, etc. In this post, I’ll give a quick introduction to using docker-compose with PHP and MySQL.
Docker helps development by bundling an application’s code with the environment where it will run. This means that if you give the codebase to another developer on your team, they won’t need to worry about installing new software or upgrading/downgrading software you’ve already installed (such as Apache or MySQL), and they won’t have to worry about changing versions.
In this post, I’m going to talk about three files:
- A Dockerfile called
Dockerfilein the root of the project
- A YAML file called
- A PHP file called
1. The Dockerfile
Here’s the whole Dockerfile we’ll be discussing:
FROM php:7.2-apache WORKDIR /var/www RUN apt-get update \ && apt-get install -y wget \ && rm -rf /var/lib/apt/lists/* RUN apt-get update \ && apt-get install -y git RUN docker-php-ext-install pdo pdo_mysql mysqli RUN a2enmod rewrite # php unit RUN wget -O /usr/local/bin/phpunit https://phar.phpunit.de/phpunit-8.phar RUN chmod +x /usr/local/bin/phpunit COPY ./src /var/www/html
Dockerfile is a convention. Unless otherwise specified, Docker will look for a file with this name. The Dockerfile is used to create an “image,” which you can think of as a snapshot of the state of really stripped-down Linux OS. It will typically be built on top of a pre-existing Docker image, and it will consist of instructions to install more needed software.
Next, we run a series of commands to install anything else we’ll need for our server, including extensions for MySQL and PHPUnit.
Finally, we copy over the contents of our
src/ directory to
/var/www/html. This is where Apache will look to serve files, as documented by the Docker Hub page on the image.
The types of tasks done here are pretty standard for Docker files. They’re mostly about installing software and getting code from your local environment to the container.
At this point, you could run Docker commands to build and run the image (here’s another post if you want to see them), but I’m going to skip those steps and go straight to using Docker Compose, because I find it more convenient for local development.
Docker Compose allows for building and running multiple containers using the
docker-compose up and
docker-compose down commands.
Here’s the Docker Compose file:
version: '3' services: mysql_database: image: mysql:5.7 ports: - '3001:3306' volumes: - ./mysql_data:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD=root_pa55w0rd - MYSQL_DATABASE=my_database - MYSQL_USER=user - MYSQL_PASSWORD=pa55w0rd my_php: depends_on: - mysql build: . ports: - '3000:80' volumes: - ./src:/var/www/html - ./tests:/var/www/tests environment: - MYSQL_DATABASE=my_database - MYSQL_USER=user - MYSQL_PASSWORD=pa55w0rd
You can see that we specify two services, one called mysql_database, and one called my_php. You could call them anything you want. We’ll come back to them in a bit.
You can see that mysql_database specifies an image of
mysql:5:7. Like the PHP image we used as a base in the Dockerfile, this will get pulled from Docker Hub the first time our container is built. The my_php service doesn’t specify an image. Instead, it provides a build directory of the project. Docker Compose will look to our local Dockerfile to build the image.
Each service exposes ports that it will use. For MySQL, this is 3306. For Apache, it’s 80. In order to bind those to the host machine’s ports, you specify port mappings in the form
- '<HOST_PORT><EXPOSED_IMAGE_PORT>' under ports.
I specify a few host volumes for each service. Volumes are a method for persisting data from containers. In mysql_database, for example, I use my local directory
mysql_data as the location for the MySQL container to store data. This means that even if I destroy the container, I still have a local copy of the database. In my_php, I use a volume for my
src directory. Because the container shares the data with my host machine, this means I can essentially write code to
/var/www/html in the container and have it take effect without rebuilding the image.
You can read more about the types of volumes here.
Lastly, I pass in environment variables to both containers. The ones used for MySQL are based on documentation for the MySQL image, and the ones for PHP will be pulled out of
$_ENV in the PHP code.
I don’t want to get into too many details about writing PHP, but I do want to show how to connect your PHP container to the MySQL one. Here’s the start of a file:
<?php $user = $_ENV["MYSQL_USER"]; $password = $_ENV["MYSQL_PASSWORD"]; $database = $_ENV["MYSQL_DATABASE"]; $conn = new mysqli("mysql_database", $user, $password, $database); // etc... ?>
This pulls out the environment variables that were specified in the
docker-compose file and uses them to connect to the database. What’s really cool here is that the first argument to
mysqli is the string “mysql_database,” rather than a URL. Docker handles the mapping for you.
With the above three files, you should be able to start the containers using
docker-compose up --build, and the server should be available at localhost:3000. The first time you run this, it may take a bit to download the images and set up MySQL. Hitting ctrl-C should stop and remove the containers.