Mark Shust

Mark Shust

Magento 2 Development with Docker on OS X


Magento 2 Development on Docker with OS X

Why Docker?

Docker has been making it’s way all around the world, and containerization is here to stay. PHP development has always been fairly easy to setup on Mac OS X, however with the ever-increasing new releases of PHP (5.4, 5.5, 5.6, 7.x…), you’ll eventually come to a point where you’ll need to manage multiple projects at the same time, all perhaps running different software versions.

For example, Magento 1.x is only supported on PHP 5.4/5.5, and Magento 2.x is only supported on PHP 5.5/5.6. While PHP 5.5 may work for both, there are numerous situations where you may not want to do this. Most web hosts are still running PHP 5.3 or 5.4, and you may want to run PHP 5.6 on your Magento 2 install to test out some new features, while still running PHP 5.4 for your Magento 1.x installations. And when PHP 7 is released, it’s all downhill from there…

Ideally, you want your dev, staging and production servers to be running the exact same setup in regards to web server, PHP modules, server extensions, and so on. There are so many variables that come into play with running different versions within different environments, and you never want to hear “Well, it runs on my dev. Why doesn’t it run on yours?“. Even if you are running something like Ubuntu from dev through prod, you’ll still have situations where system packages or software versions don’t exactly match each other between environments. And we all love to develop on our Mac ;)

So, Docker to the rescue! Docker is a way to run containers. Quoting directly from Docker, Inc.:

“Docker allows you to package an application with all of its dependencies into a standardized unit for software development.”

Perfect. If you want to read more about Docker and it’s specifics, you can continue to follow some tutorials on Docker to learn more, as this article assumes you know a little bit about it and how it works.

LAMP on Docker

ElePHPant

The thing us PHP devs need is a good LAMP stack, usually with all of the services located on the same physical or virtual machine. However, Docker likes each service to run in it’s own container. This is what makes Docker so great: your web server and database server (and even PHP if you run it with php-fpm) can each run isolated within their own container, but also still all be located on the same bare metal machine or virtualized host. This isolation helps keep all of the different services neat & tidy; working on one container will not affect the settings or functionality of the other containers.

The Docker Hub is a fantastic collection of community-developed containers. This collection will without a doubt help you learn about container setups as well as setup your own. In our case, we chose to start from the debian:jessie container, which is sort of like Ubuntu with great APT package management, but more streamlined & compact for server use. This image will contain the Nginx web server as well as PHP. We’ll run them using separate containers, as we’re using the php-fpm version of PHP.

We’ll also be using the mariadb:10.0 container for MySQL, which is similar to the mysql:5.6 image, but without being controlled by Oracle (and being more performant). We don’t have to do anything with this MariaDB/MySQL image because it’s all tested and ready to go. Using official images like these also gives us some assurance that they will be very stable & secure, and supported by the vendor.

Prerequisites for Mac OS X

Docker will work just fine on a Linux-based operating system such as Ubuntu because it can be ran natively. But OS X is Unix-based, so we’ll need to add one additional piece of functionality to be able to run Docker on our Mac.

Our first step is to install Docker, Docker Compose, Vagrant and VirtualBox. I prefer Homebrew because it’s very easy to manage and update packages.

brew update
brew install docker
brew install docker-compose
brew cask install virtualbox
brew cask install vagrant

At Mage Inferno we started off using Boot2Docker for Docker + Mac support, and it generally works ok, but only if you don’t plan to mount files from the host system. But this is exactly what we want to do — mount our local filesystem to the Docker container so we can develop natively on our Mac, just as if it’s hosted locally. Boot2Docker uses VirtualBox, which uses the vboxsf filesystem to mount remote files. The problem is that vboxsf is horrifically slow, and the situation is greatly exaggerated when mounting many files (Magento has a few files ;).

Dinghy

No worries! Dinghy to the rescue. This application uses a modified version of Boot2Docker that uses the far superior NFS filesystem to mount remote file shares. It also has some great additions us web developers can really use, including a DNS resolution service and HTTP proxy (which also very neatly runs within it’s own container in true Docker fashion). Fairly easy Magento 2 development on Docker and OS X wouldn’t be possible without something like Dinghy (at least at the time of this post), so be sure to star the package on github to give the developer some kudos. Let’s install Dinghy:

brew install https://github.com/codekitchen/dinghy/raw/latest/dinghy.rb

Creating Docker Images

A Dockerfile is a list of instructions that will be used to build a docker image. To avoid an even longer tutorial, we went ahead and built out some base Docker images so your don’t have to take the time to build them yourself. Think of these images as similar to the pre-built debian or mariadb images, that you can just plug into and use.

If you would like to know more about each image, please click on the image names above to check out their related Dockerfile and build & setup scripts. We’ll use these images to kickoff the installation and server configuration process for our containers, which will help us get Magento 2 up and running without much effort.

View all Mage Inferno images at Docker Hub

Running a Docker image

Before we continue any further, now is a good time to see if you can run a Docker image. First, you’ll need to start up the Dinghy service if you haven’t done so already, which will give you the ability to connect OS X to Docker.

dinghy up

This is the equivalent of running boot2docker up or vagrant up. You only need to do this once every time you reboot your Mac, and it will keep running in the background until the next time you reboot.

Next, we’ll test our setup by starting a mageinferno/magento2-php container first.:

docker run -d --name php-fpm mageinferno/magento2-php

This is a very simple command, which runs a container based on the mageinferno/magento2-php image, and names it php-fpm. The -d flag makes it run as a background daemon.

Next, we’ll start our web server container:

docker run -d -e VIRTUAL_HOST=mywebserver.docker --link php-fpm --name mywebserver mageinferno/magento2-nginx

The only differences here are the --link flag, which will link the php-fpm container to this image, and the setting of the VIRTUAL_HOST environment variable, which instructs Dinghy to setup the proper DNS and HTTP proxy.

At this point, we should now be able to access our container from the web browser. We can verify the container is running by the command docker ps, which lists all running Docker containers.

Open your web browser and visit http://mywebserver.docker/

We should now see a “404 Not Found” message from Nginx, which is our success sign that everything is installed correctly and up & running. We don’t have to assign ports to our docker run command, as the port is already exposed from the Dockerfile, and DNS & port forwarding is taken care of by the wonderful Dinghy HTTP proxy.

We can also access the container via SSH with:

docker exec -it mywebserver bash

We can now explore around the Debian system and do whatever we wish, just as we would a normal web server. Just typing exit will take us back to the host machine. The bash command above can also be substituted with any other command you’d normally type at a shell prompt, so we can easily run one-off commands.

The container will continue to run until it is stopped:

docker stop mywebserver

If we do docker ps now, our container appears to have been removed. However, if we execute docker ps -a we can see that this image is still around, but just dormant. We can bring it back to life by running:

docker start mywebserver

And we can delete it, by stopping the server and then executing a remove command:

docker stop mywebserver
docker rm mywebserver

We are getting a 404 error because there are currently no files being served up by Nginx. Let’s fix that with a brand new configuration & setup for Magento 2.

Docker Compose

The final piece of the puzzle is Docker Compose. This allows us to setup a simple YAML configuration file which instructs Docker to run one or more containers based on the images and parameters defined therein.

Docker Composer

First, we’ll create a new directory in our ~/Sites folder with the name of our project, and go into it:

mkdir ~/Sites/mysite
cd ~/Sites/mysite

Next, we’ll create a file within this directory named docker-compose.yml with the following contents:

app:
  image: mageinferno/magento2-nginx:1.9
  links:
    - php-fpm
    - db
  volumes_from:
    - appdata
  environment:
    - VIRTUAL_HOST=mysite.docker

appdata:
  image: tianon/true
  volumes:
    - ./src:/src
    - ~/.composer:/root/.composer

"php-fpm":
  image: mageinferno/magento2-php:5.6-fpm
  links:
    - db
  volumes_from:
    - appdata

db:
  image: mariadb:10.0
  ports:
    - "8001:3306"
  volumes_from:
    - dbdata
  environment:
    - MYSQL_ROOT_PASSWORD=magento2
    - MYSQL_DATABASE=magento2
    - MYSQL_USER=magento2
    - MYSQL_PASSWORD=magento2

dbdata:
  image: tianon/true
  volumes:
    - /var/lib/mysql

setup:
  image: mageinferno/magento2-setup:2.0
  links:
    - db
  volumes_from:
    - appdata
  environment:
    - M2SETUP_DB_HOST=db
    - M2SETUP_DB_NAME=magento2
    - M2SETUP_DB_USER=magento2
    - M2SETUP_DB_PASSWORD=magento2
    - M2SETUP_BASE_URL=http://mysite.docker/
    - M2SETUP_ADMIN_FIRSTNAME=Admin
    - M2SETUP_ADMIN_LASTNAME=User
    - M2SETUP_ADMIN_EMAIL=dummy@gmail.com
    - M2SETUP_ADMIN_USER=magento2
    - M2SETUP_ADMIN_PASSWORD=magento2
    - M2SETUP_USE_SAMPLE_DATA=true

Note that we can use this same docker-compose.yml file in other directories to spawn off another set of Magento 2 containers, and all we have to change are the two references to mysite.docker:

Note the other environment parameters which specify more installation parameters and configuration options, including Magento admin credentials, and whether or not to install the Magento 2 sample data.

During the install, there will be a lot of Composer dependencies installed (unless you’ve previously downloaded them on your Mac), and we’ll almost assuredly hit the GitHub rate limits. Magento now also checks for authentiation info, so you will need to request Composer Auth keys. It’s best to proactively setup a new GitHub auth token by visiting your GitHub Auth Tokens. When creating a token, be sure public_repo is checked. We’ll store the file on our Mac at ~/.composer/auth.json in this format:

{
    "http-basic": {
        "repo.magento.com": {
            "username": "PUBLIC_KEY",
            "password": "PRIVATE_KEY"
        }
    },   
    "github-oauth": {
        "github.com": "YOUR_ACCESS_TOKEN_HERE"
    }
}

This file will be remotely mounted to our Docker images so GitHub can successfully authenticate and download the Composer packages. If you wait for the prompt and insert the token in the setup script, it will be cloned back to your Mac, because your entire ~/.composer directory will be remotely mounted from the docker-compose file.

Now we’ll run the following command to kickoff the Magento 2 installation:

docker-compose up setup

This will create a container from the mageinferno/magento2-setup image, create and mount a folder at ~/Sites/mysite/src (which will contain all of our Magento 2 source code and project files), create the db and dbdata images (from the defined links reference), and pass in all of those environment variables, which will in turn get passed to the m2setup.sh bash file defined within the mageinferno/magento2-setup Docker image.

When the m2setup.sh bash file is executed, it will start by cloning the Magento 2 git repository from GitHub and installing all of the composer dependencies. Then it will execute the ./bin/magento setup:install command which will install Magento 2 and setup our database. Then it will finish up by installing the NodeJS dependencies and running the grunt process, which will compile and build all of the necessary frontend scripts. This entire process may take a while (~30-40 minutes depending on if Composer deps are already cached), so go make some coffee and grab a bite to eat.

Once the Magento 2 setup process is all complete, we can create our Apache + PHP Docker container to view our new Magento 2 installation. The -d flag tells us to start the containers in the background and keep them running.

docker-compose up -d app

We can now visit our site at http://mysite.docker and have fun poking around our new Magento 2 installation.

Conclusion

We hope this was a good intro into using Docker with Magento 2. The installation process compared to Magento 1 is much more complex, and we hope this will assist you in getting up and running quickly, while also maintaining a good synergy between development, staging and production.

Please note that the images at the time of this writing were intended mainly for development usage, and are not recommended to be run in a production environment. Feel free to modify the Dockerfile accordingly depending on your development needs, and be aware that code, development & deployment strategies will continue to change as the Magento 2 platform becomes more mature.