Magento 2 & Docker Tutorial – Part 2

Magento 2 & Docker Tutorial – Part 2

thumbnail
Want to talk about your project?

Part 2 – How to build own PHP docker image with needed libraries? How to store MySQL data safely?

This tutorial is a part of the big one Magento & Docker tutorial. You can find the first part here. The complete code of the previous tutorial is on the GitHub.

Take care of the complete PHP image

Last time I showed how to run a simple web application using official docker images in docker-compose. We have reached the stage where the necessary PHP libraries were missing. As I wrote previously, we can install any needed libs directly on the container but these changes are temporary. Everything that has been changed after the first launch of the container will be lost during destroying them. If we want to protect against such a situation we have to create an image with all needed libraries/applications. To do this, create a Dockerfile file in which you define the image properties.

There is a current application structure:

App
  - conf
    - app
      - etc
        - nginx
          - conf.d
            - default.conf
  - src
  - docker-compose.yml

Create new directories dockerfile -> phpfpm in the main app dir. Create a file named Dockerfile inside.

App
  - conf
    - app
      - etc
        - nginx
          - conf.d
            - default.conf
  - dockerfile
    - phpfpm
      - Dockerfile
  - src
  - docker-compose.yml

This will be our image definition. Complete Dockerfile reference you can read here.

Open Dockerfile and insert a line with FROM {base image name}. We want to create an image basing on php:7.3-fpm (as we used in docker-compose.yml). There is how the Dockerfile should look:

FROM php:7.3-fpm

file: ./dockerfile/phpfpm/Dockerfile

Next, we have to change phpfpm service definition in docker-compose.yml. 

This is the current docker-compose.yml file content:

version: '3.7'
services:
  app:
    image: nginx:1.16.1
    ports:
      - "80:80"
    volumes:
      - ./conf/app/etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
      - ./src:/var/www/html
  phpfpm:
    image: php:7.3-fpm
    volumes:
      - ./src:/var/www/html
  mysql:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: mydatabase
      MYSQL_USER: myuser
      MYSQL_PASSWORD: test123
      MYSQL_ROOT_PASSWORD: test123

file: ./docker-compose.yml

Change image: php:7.3-fpmto build: ‘./dockerfile/phpfpm’ to create a container using our own Dockerfile. Run docker-compose up -d. Docker will build the image from Dockerfile and run the container basing on it.

Building phpfpm
Step 1/1 : FROM php:7.3-fpm
---> 94e4d8c1eab2

Our application works as before. 

PHP configuration

Official PHP image documentation says:

This image ships with the default php.ini-development and php.ini-production configuration files.

It is strongly recommended to use the production config for images used in production environments!

The default config can be customized by copying configuration files into the $PHP_INI_DIR/conf.d/ directory.

Let’s use the development configuration for this example. Add RUN mv “$PHP_INI_DIR/php.ini-development” “$PHP_INI_DIR/php.ini” to the Dockerfile.

FROM php:7.3-fpm

RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"

file: ./dockerfile/phpfpm/Dockerfile

Install libraries permanently

We had to install mysqli extension manually on the container previously. In the docker file, we can define the extension installation “on build”. PHP image offers a dedicated script for extensions install called docker-php-ext-install. Let’s try to use it in our Dockerfile.

Add RUN docker-php-ext-install mysqli to the end of Dockerfile.

FROM php:7.3-fpm


RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
RUN docker-php-ext-install mysqli

file: ./dockerfile/phpfpm/Dockerfile

For up the composition with new Dockerfile run 

$ docker-compose up -d --build

(The –build option forces the image to be rebuilt.)

And go to http://127.0.0.1 to test the application. 

It works. 🙂

You can read more about extensions in the image documentation.

MySQL data

Save on a local machine

There was a problem with store data permanently because database files were saved on the container. If we don’t want to lose data, mysql directory has to be mounted to the local machine. Add volumes statement to mysql service in docker-compose.yml.

    volumes:
      - ./mysql:/var/lib/mysql

All mysql data will be stored on the local machine, and will not lose after container destroy.

Let’s try.  Destroy containers docker-compose down, and go up them again docker-compose up -d. Run application some times http://127.0.0.1

Rebuild again

$ docker-compose down
$ docker-compose up -d

And go visit http://127.0.0.1 one more time.

Data from the previous run are there.

Initial data

Every application needs a specific database structure and initial data. Mysql offers entrypoint script. We can read on documentation https://hub.docker.com/_/mysql (#Initializing a fresh instance).

When a container is started for the first time, a new database with the specified name will be created and initialized with the provided configuration variables. Furthermore, it will execute files with extensions .sh, .sql and .sql.gz that are found in /docker-entrypoint-initdb.d. Files will be executed in alphabetical order. 

All we have to do is mount the catalog /docker-entrypoint-initdb.d locally and put *.sql / *.sql.gz file into.

Create init.sql file in the new sql directory.

App
  - conf
    - app
      - etc
        - nginx
          - conf.d
            - default.conf
  - dockerfile
    - phpfpm
      - Dockerfile
  - src
  - sql
    - init.sql
  - docker-compose.yml

Move CREATE TABLE from index.php to init file.

CREATE TABLE IF NOT EXISTS `user` (
    `id` INT unsigned NOT NULL AUTO_INCREMENT,
    `email` VARCHAR(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
    `password` VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB;

file: ./sql/init.sql

And add another volume to the mysql service:

- ./sql:/docker-entrypoint-initdb.d:ro

Container shouldn’t make changes in our initial files, so we can add :ro (read-only) flag to volume declaration.

There is complete docker-compose.yml now:

version: '3.7'
services:
  app:
    image: nginx:1.16.1
    ports:
      - "80:80"
    volumes:
      - ./conf/app/etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
      - ./src:/var/www/html
  phpfpm:
    build: './dockerfile/phpfpm'
    volumes:
      - ./src:/var/www/html
  mysql:
    image: mysql:5.7
    volumes:
      - ./mysql:/var/lib/mysql
      - ./sql:/docker-entrypoint-initdb.d:ro
    environment:
      MYSQL_DATABASE: mydatabase
      MYSQL_USER: myuser
      MYSQL_PASSWORD: test123
      MYSQL_ROOT_PASSWORD: test123

file: ./docker-compose.yml

and index.php

<?php
$mysqli = mysqli_connect(‘mysql’, ‘myuser’, ‘test123’, ‘mydatabase’);
if ($mysqli) {
    if (!$mysqli->query(‘INSERT INTO user (email, password) VALUES (“testuser”, “testpassword”)’)) {
        echo $mysqli->error;
        die(‘Error’);
    }
    $result = $mysqli->query(‘SELECT * FROM user’);
    if ($result) {
        while ($row = $result->fetch_assoc()) {
            printf (“<p>#%d %s</p>\n”, $row[‘id’], $row[’email’]);
        }
        $result->free();
    }
}

file: ./src/index.php

Try to rebuild the app one more time. If we didn’t break anything everything will be working as before. Let’s remove ./mysql directory first to give a chance to init database from our init.sql file.

$ sudo rm -rf ./mysql
$ docker-compose down
$ docker-compose up -d

And check http://127.0.0.1

Looks good.

Push image

Dockerhub account

If we want to share our stack we will have to include a Dockerfile, and everybody will have to build PHP-FPM image on the local machine. We can share a build image using http://hub.docker.com. Create an account if you don’t have yet: https://hub.docker.com/signup.

Next, login to and go to Profile -> Account Settings -> Security (https://hub.docker.com/settings/security) And create the new access token here

Copy data and open terminal. Run:

$> docker login

Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.

Username: pandagroup

Password:

Login Succeeded

Now, you can push images to your repository. Free Dockerhub account allow you to store unlimited public repositories You can upgrade plan or use another tool (eg. GitLab) to store private docker images.

Name and tag

Every pushed images has to be maned and tagged. Open the terminal and go to dockerfile/phpfpm/. For build and tag image run docker build -t <<yourdockernamespace>>/<<imagename>>:<<tag>>

eg.

$> docker build -t pandagroup/php-fpm:7.3-mysqli

If all goes well you should see success message

Successfully built 0f800b1d4960
Successfully tagged pandagroup/php-fpm:7.3-mysqli

Now, we have named the image on our local machine and we can use it in docker-compose.yml. Change build in phpfpm service to image: pandagroup/php-fpm:7.3-mysqli.

version: ‘3.7’

services:
app:
image: nginx:1.16.1
ports:
- "80:80"
volumes:
- ./conf/app/etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf
- ./src:/var/www/html
phpfpm:
image: pandagroup/php-fpm:7.3-mysqli
volumes:
- ./src:/var/www/html
mysql:
image: mysql:5.7
volumes:
- ./mysql:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d:ro
environment:
MYSQL_DATABASE: mydatabase
MYSQL_USER: myuser
MYSQL_PASSWORD: test123
MYSQL_ROOT_PASSWORD: test123
volumes:
sockdata:

file: ./docker-compose.yml

Check if it working now. In terminal, go to main project directory and run:

$> docker-compose down
$> docker-compose up -d

There should be no problem.

Now, we could move the dockerffile/phpfpm directory to another location and commit a stack without this. However, I will leave these files for the tutorial. Finally, let’s push our image to the Docker repository. Run

$> docker push <>/php-fpm:7.3-mysqli 

Summary

We can already create own images with installed needed libraries. Our mysql data are safe, and database initializes from mysql dump. In the next part, we try to run Magento using our stock. I will show, how to configure Redis and varnish cache in a docker environment.

You can find all files from this part of the tutorial here:
https://github.com/pandagrouppl/docker-magento-tutorial/tree/PART-2

Sources:

...