Develop Magento modules with Docker

2020-02-04

Why

Docker is a good option for setting up local development environments. Especially on Windows, where Magento2 won't work natively. Alpine is a good base image, since it's very light, ~5MB uncompressed and ~2MB compressed. As opposed to something like Debian that starts at ~50MB compressed.

This setup works both on Windows and Linux, most likely on MacOS as well.

How

Get the Magento2 source code

This can be done either from Magento developer site, or downloading the repo as a zip from git.

Copy the zip

Copy the zip or even better make a symbolic link(it must be a hard link) in the folder with the Dockerfile This is needed because Dockerfile can't reference files outside its directory.

Create the Dockerfile

You can find the sample Dockerfile contents below.

  • The php-fpm image doesn't have the modules needed by Magento enabled by default, so those have to be enabled
  • Opcache is not enabled by default, and it makes a big difference in performance
  • The magento source code is copied into the container itself, this makes things much snappier in Docker on Windows
  • The script also installs all the composer packages required by magento

Create a docker-compose.yaml

  • Magento requires a DB and a server, docker-compose is a convenient way to launch them all at once in a single network
  • Notice that the Magento source is shared between php-fpm and nginx containers via a volume, nginx needs the magento source to match the requests, which it then forwards to the php-fpm via fastcgi
  • We reference the Dockerfile by name, since it's more convenient to use between projects, but you can also replace image: magento:2.3-alpine with build: . to just build the Dockerfile without creating an image
  • We map the port 80 from the nginx container to port 8080 on localhost, you can use any port you like on the local machine

Create site.conf file

  • This this is the nginx config, by default nginx can't properly route Magento source, so, we have to import the nginx config that comes with Magento
  • It will be copied into the nginx container to be picked up as the default configuration

Create php.ini file

  • This is a simple way to configure the php settings inside the php-fpm container
  • It will be copied into the php-fpm container

Run docker build

Run docker build (this might take a while)

  • We create a few intermediate containers for convenience, to remove them once the final container is build use the --rm parameter

Run docker-compose

Run docker-compose up

  • This will launch all the containers in docker-compose, and create a network between them

Complete the setup

Navigate to http://127.0.0.1:8080/setup or any port you chose, to complete the setup

  • Alternatively you can jump into the running php container and run something like php bin/magento setup ..
  • You can do that by running docker exec -it {folder_name}_php_1 /bin/sh
  • Then cd /var/www/html/magento
  • Then you can run any magento command php bin/magento setup:di:compile and so on
  • If you get an out of memory error, you can launch php with more RAM allocated via php -d memory_limit=1024M ... or set that in php.ini
  • Probably not worth setting up localhost as the sitename, as Magento seems to have issues with it, use a custom record in hosts file, or use 127.0.0.1

Setup the DB details

Use the following db details to configure magento, they are modifiable in the Dockerfile

  • Database host: mysql
  • Database name: magento
  • User: user
  • Password: user1

Installing the module

To install the module you'll probably want to jump into the php container via docker exec -it {folder_name}_php_1 /bin/sh, mentioned above

Query the db from via the CLI

To query the db from the cli, you'll have to start a new container in the same network

  • docker run --network {folder_name}_main-network -it alpine
  • Then install mariadb client apk add --no-cache mariadb-client
  • The run the mariadb client mysql -h mysql -u user -p and enter the password from the docker-compose
  • You can also add docker image with phpmyadmin in it, to the docker-compose

Create CLI aliases

You can create some aliases for simple access to magento command line

Bash

# Set a bash alias
alias magento='docker exec -it {folder_name}_php_1 /var/www/html/magento/bin/magento'

# Helper to get the logs
alias magento-logs='docker exec -it {folder_name}_php_1 tail -f -n 10 /var/www/html/magento/var/log/debug.log'

# Then use it to run commands against magento inside the container
magento module:status

Fish

alias magento 'docker exec -it {folder_name}_php_1 php /var/www/html/magento/bin/magento'
magento module:status

PowerShell

function magento { docker exec -it {folder_name}_php_1 php /var/www/html/magento/bin/magento $args }
magento module:status

Your directory tree should look something like this:

> tree
.
├── docker-compose.yaml
├── Dockerfile
├── magento.zip
├── site.conf
└── Vendor
    └── Module
        ├── etc
           └── module.xml
        └── registration.php

On Linux a hard symbolic link can be created via the ln command.

Example of creating a symlinc on Linux, building an image and running docker-compose up.

ln ~/Downloads/magento2-2.3.zip ./magento.zip
docker build --rm -t magento:2.3 .
docker-compose up

Attachments

Dockerfile

FROM php:7.3-fpm-alpine AS fpm-magento-base
RUN apk add --no-cache libpng-dev icu-dev libxml2-dev libxslt-dev zip libzip-dev libjpeg-turbo-dev freetype-dev  && \
    docker-php-ext-configure gd --with-jpeg-dir=/usr/include --with-freetype-dir=/usr/include && \
    docker-php-ext-install -j$(nproc) pdo_mysql bcmath gd intl soap xsl zip sockets opcache && \
    php -r "copy('https://getcomposer.org/installer', '/root/composer-setup.php');" && \
    php /root/composer-setup.php --install-dir=/usr/bin

FROM fpm-magento-base AS fpm-magento-base-with-dependencies
COPY ./magento.zip /var/www/html/magento.zip
RUN unzip /var/www/html/magento.zip && \
    rm /var/www/html/magento.zip && \
    cd /var/www/html && \
    mv magento* magento && \
    cd magento && \
    php /usr/bin/composer.phar install

FROM fpm-magento-base-with-dependencies
RUN cd /var/www/html/magento && \
    php /usr/bin/composer.phar install && \
    php bin/magento module:enable --all && \
    php -d memory_limit=1024M bin/magento setup:di:compile && \
    mkdir var/cache && \
    chmod -R 777 var && \
    chmod -R 777 app && \
    chmod -R 777 pub && \
    chmod -R 777 generated && \
    php /usr/bin/composer.phar clearcache

docker-compose.yaml

version: "3"
services:
  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - app:/var/www/html
      - ./site.conf:/etc/nginx/conf.d/default.conf
    networks:
      - main-network
    depends_on:
      - php
  php:
    image: magento:2.3
    networks:
      - main-network
    volumes:
      - app:/var/www/html
      - ./Vendor:/var/www/html/magento/app/code/Vendor
  mysql:
    image: jbergstroem/mariadb-alpine
    environment:
      MYSQL_USER: user
      MYSQL_PASSWORD: user1
      MYSQL_DATABASE: magento
    expose:
      - "3306"
    volumes:
      - db:/var/lib/mysql
      - ./php.ini:/usr/local/etc/php/php.ini
    networks:
      - main-network
networks:
  main-network:
    driver: bridge
volumes:
  db:
    driver: local
  app:
    driver: local

site.conf

upstream fastcgi_backend {
  server  php:9000;
}

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

php.ini

max_input_vars 10000
memory_limit 1024M