Why use containers?

Containers revolutionized infrastructure, providing a lightweight alternative to other virtual systems. Because they share resources they are able to use less resources with more but add more granularity to management and scaling. Also, they are easier to share across environments while keeping consistency.

Containers can be used in both production and development or any environment that uses a server. While a simple setup may include only a single instance of a type, a production environment can multiply the number for a more granular scaling.

In a local development instance, containers can help with resource partitioning; removing the overhead of a Virtual Machine but still keeping the resources isolated. In production they can be used for various scalability scenarios, allowing for a more granular scaling than the classical models.

Requirements

For this tutorial we will setup a simple container like you would use for a local development environment. Docker and Docker Compose have to be installed on the machine. Installation instructions can be found at: https://docs.docker.com/engine/installation/

To identify the requirements for the container and the isolation we can use the dev pages: http://docs.magento.com/m2/ce/user_guide/magento/system-requirements.html

From this document the entities can be identified:

  • php container
  • web server container
  • database container

Beside these basic containers there are also some optional containers or connections:

  • Cache Storage
  • Session Storage
  • Reverse proxies (Varnish)
  • Search
  • RabbitMQ

Some cloud service providers offer special services for database, caching and even search.

Keep in mind that with the exception of database, all other services are optional and the database or the file system will be used instead. However, specialized services or containers should be taken into consideration as they can have a big impact on performance.

This tutorial will cover the PHP FPM, Nginx and connection with caching systems.

Project Structure

Docker projects by default use a folder with a file named Dockerfile inside it.

In this example the folder structure will be:

- project/
  - docker-compose.yml
  - php/
    - Dockerfile
  - nginx/
    - default.conf
    - Dockerfile
  - source/

The source folder will contain the Magento2 project source code. To simplify the tutorial, we will consider that the project is already build with Composer and includes the dependencies.

The PHP container

In this tutorial, the base container used will be https://hub.docker.com/_/php/

From the dev page (http://docs.magento.com/m2/ce/user_guide/magento/system-requirements.html) we can identify the current requirements:

- bc-math (Magento Commerce only)
- curl
- gd, ImageMagick 6.3.7 (or later) or both
- intl
- mbstring
- mcrypt
- mhash
- openssl
- PDO_MySQL
- SimpleXML
- soap
- xml
- xsl
- zip

These requirements may change over time so it's a good idea to check the Dev Docs before proceeding.

The used PHP version is 7.1 which is compatible with Magento 2.2 and onwards.

The container already contains PHP and some extension so only some extensions will be added.

The content of the Docker file inside project/php/Dockerfile will be:

FROM php:7.1-fpm

; install libraries required by the extensions
RUN apt-get update \
  && apt-get install -y \
    libicu-dev \
    libjpeg62-turbo-dev \
    libmcrypt-dev \
    libpng12-dev \
    libxslt1-dev

; install extensions
RUN docker-php-ext-install \
  bcmath \
  gd \
  intl \
  mbstring \
  mcrypt \
  pdo_mysql \
  soap \
  xsl \
  zip

; creating php.ini file
RUN echo 'memory_limit = 2G\n\
max_execution_time = 1800\n\
zlib.output_compression = On' >> /usr/local/etc/php/php.ini

; copy the source from the directory "source" into the container
ADD source /var/www/html

WORKDIR /var/www/html

The script is installing remaining extensions and adding some Magento specific settings tot the php.ini file.

The container now needs to be added to the docker-compose file. The new docker-compose.yml file will contain:

version: '3'
services:
  php:
    build: php
    volumes:
      - ./source:/var/www/html

Now to check if everything is working the container must be build:

$ docker-compose build

The result should be: Successfully built

The Nginx container

Unlike other web apps, Magento 2 comes bundled with the configuration for the Nginx server. This configuration file greatly simplifies the build configuration.

For better configuration of the Nginx server, an external configuration file will be used for build. This will allow to have a better view over the configuration.

The container used in this tutorial is: https://hub.docker.com/_/nginx/

The configuration file, located in nginx/default.config contains:

upstream fastcgi_backend {
 server  php:9000;
}

server {
 listen 80;
 server_name www.example.com;
 set $MAGE_ROOT /var/www/html;
 include /var/www/html/nginx.conf.sample;
}

The file nginx.conf.sample is in the Magento source code. Please note that the server name must have the proper value.

The configuration directive php:9000 is referring to the PHP-FPM container, where "php" is the alias from the docker-compose file and 9000 is the default port used.

The docker file for the Nginx container will only have the simple job of adding the configuration.

The content of nginx/Dockerfile is:

FROM nginx:1.11

COPY default.conf /etc/nginx/conf.d/default.conf

To take this container into consideration we must add it to the docker-compose.yml file. The new content will be:

version: '3'
services:
  php:
    build: php
    volumes:
      - ./source:/var/www/html
    networks:
      - appnet

  nginx:
    build: nginx
    ports:
      - 80:80
    volumes:
      - ./source:/var/www/html
    networks:
      - appnet

networks:
  appnet:
    driver: "bridge"

To simplify the communication between the PHP-FPM and Nginx containers a network was added. This allows for the containers to use their own private network and removing the need to explicitly define dependencies between containers.

As an alternative, the build can be completely removed, and mapping can be used for the configuration file. The advantage is that if the container contains the configuration file then it becomes independent and can be deployed without this dependency.

Adding a database container

As mentioned before, the DB container is sometimes optional as hosting providers sometimes offer special services which can remove some of the scaling complexity. However, for the purpose of this tutorial a container will be used.

There is no special configuration needed for the database container and there are several very good options for containers that should be taken into consideration.

In this tutorial the container used is: https://hub.docker.com/_/mysql/

Unlike other containers, the database container will need a place to store the information, for this reason a volume is added.

The updated docker-compose file will be:

version: '3'
services:
...

  db:
    image: mysql:5.7
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - 3306:3306
    environment:
       MYSQL_ROOT_PASSWORD: root
       MYSQL_DATABASE: magento2
       MYSQL_USER: magento2
       MYSQL_PASSWORD: magento2
    networks:
      - appnet

volumes:
  db_data:
    driver: "local"
networks:
  appnet:
    driver: "bridge"

The port is optional but can be useful to connect to the database from outside.

Now, Magento has to know where to connect. In the configuration file located at source/app/etc/env.php the configuration must be:

  'db' =>
  array (
    'connection' =>
    array (
      'default' =>
      array (
        'host' => 'db',
        'dbname' => 'magento2',
        'username' => 'magento2',
        'password' => 'magento2',
        ...
      ),
    ),
  ),

Please note the database host as containers must use names to call each other.

Adding cache containers

Caching containers are very easy to use with Docker because of the little configuration that is needed for them.

Redis for caching is recommended for Magento 2.

In this tutorial the container used is: https://hub.docker.com/_/redis/

Since cache is volatile there will be no storage for them, we only need them to be added to the config.

The new docker-compose.yml file will be:

version: '3'
services:
...

  cache:
    image: redis:alpine
    networks:
      - appnet

  fpc:
    image: redis:alpine
    networks:
      - appnet
...

Two cache instances were added, one for FPC and one for regular cache. Alternatively, a single cache instance could have been added and use different databases.

Now the details must be added in the Magento 2 configuration file located at source/app/etc/env.php:

  'cache' => 
  array (
    'frontend' => 
    array (
      'default' => 
      array (
        'backend' => 'Cm_Cache_Backend_Redis',
        'backend_options' => 
        array (
          'server' => 'cache',
          'port' => 6379,
          'database' => '0',
        ),
      ),
      'page_cache' => 
      array (
        'backend' => 'Cm_Cache_Backend_Redis',
        'backend_options' => 
        array (
          'server' => 'fpc',
          'port' => 6379,
          'database' => '0',
        ),
      ),
    ),
  ),

Adding a different storage for sessions

Memcached is recommended for storing sessions in Magento 2.

In this tutorial the container used is: https://hub.docker.com/_/memcached/

To be able to save data in Memcached, the php container must have the memcached extension installed.

The updated php file in php/Dockerfile is:

FROM php:7.1-fpm

RUN apt-get update \
  && apt-get install -y \
    libicu-dev \
    libjpeg62-turbo-dev \
    libmcrypt-dev \
    libpng12-dev \
    libxslt1-dev \
    libmemcached-dev

RUN docker-php-ext-install \
  bcmath \
  gd \
  intl \
  mbstring \
  mcrypt \
  pdo_mysql \
  soap \
  xsl \
  zip

RUN pecl install memcached \
  docker-php-ext-enable memcached

RUN echo 'memory_limit = 2G\n\
max_execution_time = 1800\n\
zlib.output_compression = On' >> /usr/local/etc/php/php.ini

WORKDIR /var/www/html

The PHP container must be build again using: docker-compose build

The container must be added to the docker-compose.yml file:

version: '3.1'
...
  session:
    image: memcached:alpine
    command:
      - '--memory-limit=512M'
    networks:
      - appnet

...

Now the details for the memcached container must be added in the Magento 2 configuration file located at source/app/etc/env.php:

  'session' => 
  array (
      'save' => 'memcached',
      'save_path' => 'session:11211'
  ),

These are the minimum steps required to setup a container and the things to watch out for. Using a container is helpful because the system resources are better utilized, and each server is encapsulated. Also, a container can be used on both local and production, making environment related issues a thing of the past. With containers there is no longer a need to replicate the server and its configuration, the entire server container can be pulled and used for debugging.