Practical Docker - Apache, PHP and MySQL (CakePHP example)

I will begin this series of posts about docker with a practical example using a simple Apache + PHP with MySQL setup. We’ll be using a basic CakePHP application as the example Web App.

This guide assumes that you have already done the docker setup in your development machine.

MySQL

We’ll be using a separate docker container for MySQL, for the sake of isolation and also because it will bring benefits when you are trying to scale out your application. For example, you may want to run MySQL in a separate machine for performance, or have multiple MySQL hosts load balanced to handle the scale of your app.

In the file below, you have many sections that are commented-out and that you can customize for your own environment. For example, you may choose to avoid the MySQL root user at all, and create your custom user. I’ve also set the default charset and collation to utf8mb4 / utf8mb4_general_ci, but you can change it to what makes more sense to you.

The final Dockerfile for MySQL looks like this:

FROM mysql/mysql-server:latest

# ROOT PASSWORD
# to secure your installation, you should avoid MYSQL_ROOT_PASSWORD and 
# instead set MYSQL_RANDOM_ROOT_PASSWORD and MYSQL_ONETIME_PASSWORD to yes

ENV MYSQL_ROOT_PASSWORD=secret
#ENV MYSQL_RANDOM_ROOT_PASSWORD=yes
#ENV MYSQL_ONETIME_PASSWORD=yes

# NEW USER AND DATABASE
# you can specify the name of a database to be created during initialization.
# if you specify also an username and password, the mysql user will be created 
# with full control over that database.

ENV MYSQL_DATABASE=sample-database
#ENV MYSQL_USER=sample-username
#ENV MYSQL_PASSWORD=sample-password

# SERVER DEFAULT CHARSET AND COLLATION

RUN sed -i "/default-character-set/d" /etc/my.cnf
RUN sed -i "/\[mysqld]/a skip-character-set-client-handshake" /etc/my.cnf
RUN sed -i "/\[mysqld]/a collation-server=utf8mb4_general_ci" /etc/my.cnf
RUN sed -i "/\[mysqld]/a character-set-server=utf8mb4" /etc/my.cnf
RUN sed -i "/\[mysqld]/a init_connect= 'SET NAMES utf8mb4' " /etc/my.cnf
RUN sed -i "/\[mysqld]/a init_connect=‘SET collation_connection = utf8mb4_general_ci' " /etc/my.cnf

EXPOSE 3306

To use this, you need to run the following commands (in this case, I’m running this while inside the folder where the Dockerfile is, hence the “dot” after the image name):

docker build -t danielbcorreia/mysql .
docker run -d -p 3306:3306 --name mysql danielbcorreia/mysql
docker ps -a

You’ll see something like this:

CONTAINER ID        IMAGE                           COMMAND                   CREATED             STATUS                         PORTS                    NAMES
1d7da0649a04        danielbcorreia/mysql            "/entrypoint.sh mysql"    30 seconds ago      Up 4 seconds                   0.0.0.0:3306->3306/tcp   mysql

This means that your container is now active, and you can try to connect using any MySQL client. I’ve used MySQL Workbench for this, keep in mind that the host is the IP address of your Virtual Machine (if you are using Windows) or localhost if you are using your own linux box. The username and password are the ones specified on the Dockerfile. If you used the same as above, then you can access with root/secret.

If you were able to connect, that means that your container is now running correctly.

Apache + PHP

Before starting, you will need to install/download CakePHP into a folder, usually you can do it this way:

curl -s https://getcomposer.org/installer | php
php composer.phar create-project --prefer-dist cakephp/app my-app

We’ll be working inside the “my-app” folder that composer created for CakePHP. First we need to check the requirements for our web application. Since we are using CakePHP, we’ll need the following extensions:

  • intl
  • mbstring
  • pdo_mysql

We’ll also need to enable mod_rewrite for Apache. Since we are messing with the PHP extensions, we’ll add some common ones like GD and mcrypt.

This is the final Dockerfile (note that i’m using PHP7, but you can change it according to the tags here PHP Docker Hub):

FROM php:7-apache
COPY . /var/www/html/

RUN a2enmod rewrite

RUN apt-get update && apt-get install -y \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libmcrypt-dev \
        libpng12-dev \
        zlib1g-dev \
        libicu-dev \
        g++ \
    && docker-php-ext-configure intl \
    && docker-php-ext-install -j$(nproc) iconv mcrypt intl pdo pdo_mysql mbstring \
    && docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd

RUN chmod -R 777 /var/www/html/tmp/
RUN chmod -R 777 /var/www/html/logs/

EXPOSE 80

To run this, you need to run the following:

docker build -t danielbcorreia/cakephp-apache .
docker run -d -p 80:80 --name cakephp danielbcorreia/cakephp-apache
docker ps -a

Note that right now, both containers are independent from each other. Your should be able to access http://{your-ip} and see the CakePHP application running, but with a warning regarding the database connection.

To fix this, we’ll begin with changing the CakePHP configuration file (for CakePHP 3 you should change the /config/app.php file). Search for the datasource section and replace the configuration to match your MySQL Dockerfile configuration:

'Datasources' => [
        'default' => [
            ...
            'host' => 'mysql', /* this is the name that you provided on the --link parameter of docker */
            'username' => 'root',
            'password' => 'secret',
            'database' => 'sample-database',
            'encoding' => 'utf8mb4',
            ...
        ],
        ...
    ]

Note that the “host” property is the name of the link that we will provide in the next step, not the image name!

To link these two images together, you should first rebuild the cakephp image:

docker stop cakephp
docker rm cakephp
docker build -t danielbcorreia/cakephp-apache .

Then run the image, linking it to the mysql container:

docker run -d -p 80:80 --name cakephp --link mysql:mysql danielbcorreia/cakephp-apache

IF you access http://{your-ip} now, you should have the CakePHP page with the MySQL connection active.