Magento 2 & Docker Tutorial – Part 1

Magento 2 & Docker Tutorial – Part 1

thumbnail
Want to talk about your project?

Magento 2 & Docker Tutorial

Part 1 – Simple PHP application with Nginx, PHP-FPM, and Mysql

Containerization has become commonplace in many projects. Well-designed containers make work easier. Thanks to their use it is easy to switch between many projects that require different environments. This situation can also happen when working with multiple Magento stores. The production environments can have different PHP or Database versions. They may also differ in the services used. In such a situation, Docker comes to the rescue.
In this tutorials series, we will run base Magento store using official Nginx, PHP, and Mysql docker images.

In this part, you will learn how to build a docker-compose.yml file and run a simple PHP app in Docker.

If you want to learn more about Docker, check the official project documentation: https://docs.docker.com/get-started/

Install docker and docker-compose

In this article I use these versions:

  • Docker 19.03.3
  • Docker-compose 1.24.1

Install docker using: https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-using-the-repository

And docker-compose:
https://docs.docker.com/compose/install/

After installation docker needs to use sudo for executing commands. It can be fixed easily. Just run commands:

sudo groupadd docker
sudo gpasswd -a $USER docker
sudo service docker restart

Create the docker composition

Let’s start with creation docker-compose.yml in the main project directory. Docker-compose.yml is the default file used by docker-compose to build the environment. We will add only one line to docker-compose.yml for now.

version: '3.7'

file: ./docker-compose.yml

Information about used images and these dependencies will be in this file in the future.

Nginx

The first needed image is Nginx.

You can find docker images and instructions for them on https://hub.docker.com.

Ok. Add service named app to docker-compose.yml file. The latest stable Nginx docker image version is 1.16.1

 

services:
  app:
    image: nginx:1.16.1

Whole file:

version: '3.7'

services:
  app:
    image: nginx:1.16.1

file: ./docker-compose.yml

Now, you can run docker-compose up -d for build and run the application.

$ docker-compose up
Creating network "magento_tuorial_default" with the default driver
Creating magento_tuorial_app_1 ... done
Attaching to magento_tuorial_app_1

As you can see, the network named like your project directory has been created. But we are interested in magento_tutorial_app_1. This is our Nginx app. You can also run docker-compose up with -d (Detached mode). All images will be run in the background then.

Nginx image is running, but we can’t see anything from the browser because the docker does not expose any ports by default. We have to add ports statement to a docker-compose.yml file. Default Nginx port is 80 so let’s try with this configuration:

services:
  app:
    image: nginx:1.16.1
    ports:
      - "80:80"

Now run docker-compose restart command and go to http://127.0.0.1 in a web browser.

You should see default Nginx welcome page.

Now we want to add files to our website. We need to create an Nginx configuration file and pass it to the container. We can do it by adding a volumes statement to the app service in docker-compose.yml.

Let’s create the default.conf file. I will create it in ./conf/app/etc/nginx/conf.d/default.conf.

server {
  listen 80 default_server;
  server_name _;
  root /var/www/html;
}

file: ./conf/app/etc/nginx/conf.d/default.conf

And mount it to container. Add volumes after ports statement.

    volumes:
      - ./conf/app/etc/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf

Next, create ./src directory – it will be your source code dir. Create sample index.html file in ./src

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
    <title>This is my docker configured website!</title>
</head>
<body>
<h1>This is my docker configured website!</h1>
</body>
  </html>

file: ./src/index.html

Now we have to put our source code in container. We can also mount it as the volume. Simply add this line to volumes in app service.

      - ./src:/var/www/html

This is how docker-compose.yml file looks 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

Ok, rebuild the composition now. Run:

$ docker-compose down
Stopping magento_tuorial_app_1 ... done
Removing magento_tuorial_app_1 ... done
Removing network magento_tuorial_default

$ docker-compose up -d
Creating network "magento_tuorial_default" with the default driver
Creating magento_tuorial_app_1 ... done

Let’s run our website now: http://127.0.0.1/.

If you can see It, it is working!

PHP-FPM

Nginx does not support PHP default. So we must configure PHP separately. 

Let’s find the best official PHP image on hub.docker.com. 

I think 7.3-fpm will be best. Add the new service named phpfpm (you can name services as you want, but remember to use the right name in all places).

  phpfpm:
    image: php:7.3-fpm

Now we need to convince Nginx to use a PHP processor. Please open the default.conf file and modify it by adding PHP files information in the server part.

  location ~ \.php$ {
  try_files $uri =404;
  fastcgi_split_path_info ^(.+\.php)(/.+)$;
  fastcgi_pass phpfpm:9000;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_param PATH_INFO $fastcgi_path_info;
  }

And let Nginx know that the index.php is the base website file by adding index index.php inside the server part too.

There is how the whole file looks now:

server {
  listen 80 default_server;
  server_name _;
  root /var/www/html;
  index index.php;
  location ~ \.php$ {
  try_files $uri =404;
  fastcgi_split_path_info ^(.+\.php)(/.+)$;
  fastcgi_pass phpfpm:9000;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_param PATH_INFO $fastcgi_path_info;
  }
}

file: ./conf/app/etc/nginx/conf.d/default.conf

What is phpfpm:9000? Docker-compose is creating an internal network to communicate between containers. So we can use service names inside. 9000 – this is the default php-fpm port.

Now we must to mount our code to phpfpm service container by adding volumes statement:

    volumes:
      - ./src:/var/www/html

So, there is docker-compose.yml file 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:
    image: php7.3-fpm
    volumes:
      - ./src:/var/www/html

Change ./src/index.html file to ./src/index.php and write something inside them.

<?php
    phpinfo();

Rebuild environment docker-compose down, docker-compose up -d and visit http://127.0.0.1/.

Did you see the phpinfo site? Great, our application is working!

Mysql

We have a working PHP website now. Let’s try to add the MySQL server to this configuration.
Add another service to docker-compose.yml. I suggest to use mysql:5.7.

  mysql:
    image: mysql:5.7

Docker allows passing environment variables to built containers. Mysql image supports these variables:

  • MYSQL_ROOT_PASSWORD (required)
  • MYSQL_DATABASE – the name of a database to be created on image startup
  • MYSQL_USER, MYSQL_PASSWORD – MySQL will create a new user on image startup
  • MYSQL_ALLOW_EMPTY_PASSWORD
  • MYSQL_RANDOM_ROOT_PASSWORD
  • MYSQL_ONETIME_PASSWORD

All available and these definitions you can find on https://hub.docker.com/_/mysql.

Let’s set up Mysql in docker-compose.yml.

  mysql:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: mydatabase
      MYSQL_USER: myuser
      MYSQL_PASSWORD: test123
      MYSQL_ROOT_PASSWORD: test123

After adding this and rebuilding images we will have a complete environment with Nginx, PHP, and MySQL.

Run docker-compose down, docker-compose up -d. 

If you want to execute something inside a container built by docker-compose use docker-compose exec command. Run docker-compose exec -u myuser -p to check if the configuration is working.

$ docker-compose exec mysql mysql -u myuser -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.29 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

You should see something like this. Run show databases; to check if mydatabase was created.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mydatabase         |
+--------------------+
2 rows in set (0.01 sec)

It seems to work.

Run complete app

Try to change our ./src/index.php file to execute sql query. My file looks that:

<?php
$mysqli = mysqli_connect('mysql', 'myuser', 'test123', 'mydatabase');
if ($mysqli) {
$mysqli->query('
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;
');
if (!$mysqli->query('INSERT INTO user (email, password) VALUES ("testuser", "testpassword")')) {
    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();
}
}

Visit http://127.0.0.1/

Did you see something like this? Don’t panic!

PHP image does not have installed all modules by default. You can fix it by install mysqli manually on the container.

Official PHP image has scripts for managing extensions. Run 

$ docker-compose exec phpfpm docker-php-ext-install mysqli

And restart the container:

$ docker-compose restart phpfpm

And visit http://127.0.0.1/ again.

Our application is working!

Conclusion

We have created a PHP application using official Docker images. We have the working PHP, MySql with configured database and user, and Nginx connected with php-fpm.

There is one problem. Every change made on the container after build will be lost during a rebuild. This is not a big issue if we have to install only one PHP module but there is a lot of additional configuration needed usually. This problem also affects the database. After a rebuild, all changes will be lost. 

In the next part of this tutorial I will show you how to build your own image based on the official image, it will help to save configuration permanently. We will mount the database volume and add some improvements to docker-compose.yml to make our application faster.

Complete code from this part you can find in https://github.com/pandagrouppl/docker-magento-tutorial/tree/PART-1

See you soon.

Jakub Ciszak

Sources:

...