In the last year the Docker project got a lot of adoption due to its simpler and streamlined use flow, portability across systems, application isolation, lower resources footprint, container versioning and the list can go on. A big part of this rise in adoption is also due to its team paying more attention to the community and community events. I myself got started after a local Docker community meetup, where a discussion with a mentor helped me understand the Docker ecosystem and flow.
An aspect I really like about working with Docker is the fact that it solves a real problem, mainly found in long term projects: the time spent configuring a working environment for the project. One recurring issue I keep seeing in projects is that each time we need to add a new environment, either because a new team member joins the project or we need a new test environment, we lose a lot of time looking for a specific version of the software and then configuring it. This is an even bigger issue if the project relies on older versions of the software, for example PHP 5.3, as it was the case for many projects using Magento 1.
Some colleagues might argue that tools like Ansible, Chef or Puppet have improved a lot and the provisioning of a machine can now be automated. This is true for virtual machines as well as local development environments.
Automation tools seem to be solving the problem of wasted time when searching for specific versions of software. But, because all these tools rely on downloading the required software only when provisioning the new machine, they cannot guarantee that exactly the same versions of the software will be installed. Applications evolve during their lifetime, adding improvements or dropping less used functionalities. Software repositories can be moved or even shut down - see the case with left-pad npm package - and thus we get back to square one. Of course, we can build our own software repositories, but that has its own set of issues and it’s more of a solution for big companies than for small teams.
Since Magento projects are usually ongoing or long term projects, this is an issue that needs to be taken into account or it can escalate into a serious problem.
Docker addresses this issue quite well by using something it defines as “images”. Once an image is built, maybe at the start of a project, that same image can be distributed to the entire team or it can spawn new environments that can be easily removed when they are no longer needed.
Using Docker images, a new developer joining the team can start working in minutes, regardless at what stage of the project that he/she comes in. Using Docker for a development environment will help a new developer get started on a task or analyse the project using the exact same environment as any other colleague in the project.
With Docker images, we are no longer constrained to manage large software repositories. It also makes it easier to share an environment with all members of the team even if the team is scattered all around the globe.
Setting up Docker for Magento 2 applications
A Docker community accepted workflow is to use a single service[1] per container. By decoupling the three main services needed by a Magento application, the PHP engine, the server and the database, we notice another big advantage offered by Docker: switching between different versions of these dependencies becomes easier and with no impact upon the project. Thus, Docker allows us to test a new version of the database, or a new version of PHP, or even a different stack architecture without worrying that our setup will be compromised in case of a rollback.
The team at Evozon has prepared a quick Docker bootstrap project for Magento 2 following the one service per container rule. The structure is displayed below:
``` [Project Folder] | | .project-env | docker-compose.yml | ... +---.docker | | .project-env.dist | | | +---database | | | Dockerfile | | | | | +---data | | | | .gitkeep | | | | | \---setup | | 20170111114749-Magento-21-Default-Schema.sql | | | +---nginx | | | Dockerfile | | | | | +---conf | | | nginx.conf | | | upstream.conf | | | | | \---sites-enabled | | project.conf | | | \---php | | Dockerfile | | | +---conf | | php.ini | | project.pool.conf | | xdebug.ini | | | \---logs | .gitkeep | +---app | ... +---bin | ... +---dev | ... +---lib | ... +---phpserver | ... +---pub | ... +---setup | ... +---update | ... +---var | ... \---vendor ```
NOTE: the Docker bootstrap project for Magento 2 is available on the Evozon Github repository under the MIT licence.
As we can notice from the structure above, we will build three containers: one for Nginx, one for PHP and one for the database containing the MySQL server.
When building the services defined in the project's `docker-compose.yml` file, Docker makes use of environment variables to configure the database service. To help us, we can find a template file under `.docker/project-env.dist`.
Just copy the file to the project's root folder, rename it by removing the `.dist` part from its name and edit the existing values or add new ones as needed.
NOTE: Be careful - this file contains environment variables specific to each individual system and should be kept out of the code repository.
Now, by running a `docker-compose up -d` command, we will get a development environment ready for us to start working on our new Magento 2 module.
Configuring the Docker services for Magento 2 applications
As we notice, the Docker startup project for Magento 2 proposed by the Evozon team uses the good practices established by the Docker community and it splits the stack into three different containers.
Each container has its own space for configuration files and all are already configured to be used by the containers themselves.
This project also automatically configures a default database for a Magento 2.1 application, so we can skip all the installation steps.
If we need to make changes to the default database schema, we can add additional SQL scripts inside the `.docker/database/setup` folder. To use an existing database, we can change the folder where the container is storing the data to the folder where the existing MySQL instance stores its data.
The MySQL container is configured to automatically use the new scripts in alphabetical order or the MySQL data files in case they are provided.
Configuring XDebug and PHPStorm
The bootstrap project for Magento 2 automatically installs XDebug when building the PHP image and container and it loads the XDebug configuration file from `.docker/php/conf/xdebug.ini`. If we need to change any XDebug configuration or add additional options, we can do this by simply editing this file and restarting the PHP container.
Once we’ve configured XDebug, we need to instruct PHPStorm to listen for all incoming connections. We do this by clicking the `Run > Start Listening For PHP Debug Connections` menu. The first time we perform an action that requires Magento to be executed, we will be prompted with a dialog similar to the one below:
After selecting the corresponding server configuration, or creating a new one if needed like in the image below, we can proceed with the debugging process.
To investigate any issues with the XDebug connection, we can enable the XDebug log by uncommenting the `xdebug.remote_log` line in the `.docker/php/conf/xdebug.ini` file. Do not forget to restart the PHP container by running `docker restart container_id` - replace container_id with the ID of the container on your machine.
Different environments, scaling and an always evolving project
As time progresses, projects evolve and requirements change. We may start with only three Docker services for our Magento application, but, as the shop gets an increasing number of visitors, we may require adding some caching capabilities. This means that the development environment will have to change. By using Docker, this change will only require adding a new service definition in the `docker-compose.yml` file for the new caching service. The new service will then be created automatically on all the environments where the new configuration is deployed.
How about adding additional web servers or clustering the database? For once, it’s as easy as it sounds. We just add the new services into the Docker configuration files - `docker-compose.yml` and, if needed, create a `Dockerfile` for each new service. We then update each system’s configuration as needed, configuring the database cluster or configuring the web server proxy.
The last step now is to distribute the new or updated files to the other team members and Docker will handle it from there.
Different teams will have different needs. For example, the front-end team will require an additional set of tools to complete their work. With Docker we can easily handle different environment needs by using a `docker-compose.override.yml` file. Here, we can define the additional containers needed to build the front-end part of our Magento 2 shop. The benefit of this feature is that these containers will only be created for the front-end team, while the back-end team or a testing team will not even know about them. As such, we can keep each team’s environment clean and avoid using resources for tools that are not required.
Final notes
Docker is quickly gaining grounds in the virtualization environment field due to its impressive list of benefits, a few of which we've seen in this article:
- same exact environment across the entire development team, including new members;
- quick ramp-up of new team members due to the already built infrastructure;
- easy spawning and removal of environments;
- environment standardization and implementation of version control.
A few other noteworthy benefits that Docker provides are the simplified configuration system for building template images, a leaner learning curve, application isolation and speed improvements over traditional virtual machines.
While we can go on and on listing the benefits of Docker, we can agree that working experience leads to faster learning. With this in mind, head on over to the Evozon Github repository, download the bootstrap project for Magento 2 and let us know what you think in the comments section below.
PS: pull requests for fixes and new functionalities are always welcome.
[1]: A service, in Docker terms, is any one application or system that can perform a task. This means that a service can be any application, like Nginx or PHP, or an entire system composed of multiple applications running as a single application - e.g. Gitlab is a single system composed of multiple smaller applications.
Share on