Getting started with Docker for local development

A simple introduction to Docker and Docker Compose

In this post I’m going to explain how to create a Docker local development environment. One of the very cool things about Docker is that you can deploy these containerized builds to production - however I’m not covering the additional considerations and security requirements to do that here. My aim is to end up with a local development environment that replaces tools such as MAMP, or a Vagrant based VM solution.

This tutorial will get you from zero to a functioning LAMP install at localhost, ready to install Runway and start work on your site. It is essentially my notes of the process I went through to start working locally with Docker. In future articles I’ll take a look at some more details for fine-tuning your development environment.

What is Docker?

Docker enables you to run your application in containers. This means that your site and all of the things it depends on to run - PHP version, installed modules, database - can be packaged and easily moved from one system to another.

Docker fits nicely within modern front-end development workflows. You can package your Dockerfile and other files right with the code of your project and check it into source control. This makes moving your development to another machine far easier, and helps in sharing the environment with another developer on your team.

Getting started

The first thing you need to do is install Docker from the Docker website, there are versions for Mac, Windows and Linux. The installation includes a tool called docker-compose which we will be using to create a nice shareable development environment.

Folder Structure

It might be helpful to start with an overview of where everything is going to live. The neat thing about Docker is that the information that sets up the development environment for your site can be version controlled along with the code for your site. So my simple-docker project is in a folder with the following structure. You could use the project name for your project instead of simple-docker.

Folder structure
The folder structure for my Docker project

A basic LAMP setup

The first thing I want to get running are the basic server requirements for a PHP application. For me this is going to be Apache, PHP, MySQL. This will also help us test that Docker is running nicely.

Inside the www folder, save a file named index.php with the following contents.

<?php phpinfo(); ?>

This function will show us information about the running PHP environment we configure. So we can see things work as we expect.

Inside the webserver folder you need a file named Dockerfile with no file extension. This file is going to contain the information that the Apache and PHP installation needs to get going. For now we just need to add one line.

FROM php:7-apache

What we are doing here is getting one of the official PHP images from the Dockerhub. Before the colon is php as we want a package from user php, and afterwards is the actual package I have chosen. I’m going for a combined PHP and Apache package and 7-apache gets the latest version of PHP 7. You could choose 5.6-apache if your production server is still on 5.6.

Save the Dockerfile, and then open or create a file named docker-compose.yml in the root of your project directory. This file should contain the following.

version: '2'

services:
  webserver:
    build: ./docker/webserver
    image: runwaytest_web
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /Users/rachel/Docker/simple-docker/www:/var/www/html
    links:
      - db

  db:
    image: mysql:5.7
    ports: 
      - "3306:3306"
    volumes:
      - ./db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=docker
      - MYSQL_DATABASE=db_runwaytest

The version states that we are using Version 2 of compose.

Then we have our services section. This will detail all of the services our app consists of.

I start with webserver, this is our Apache and PHP container. I’m defining some ports that should be used by this service, mapping them to the host port. So port 80 (the default port that Apache runs under) on my Mac, will be mapped directly to port 80 in the container.

The volumes section is where we map a volume on our computer to the container. This means we can edit files on our computer and serve them via the container. I am mapping the www folder that has my index.php inside to var/www/html.

Finally under links I have included db. This is a link to another container, in this case a container for MySQL.

Configuring MySQL

MySQL will be a second service, rather than create a Dockerfile for MySQL I’m going to directly request the image from the docker-compose.yml file. I do this with image, and I am requesting an official MySQL image from Dockerhub.

I’m creating a volume inside the container for the data. Here I am mapping the directory named db inside my project folder to the MySQL directory. This is important, we want to store our MySQL data inside the project as otherwise when we destroy the container we will lose anything from inside MySQL.

Under environment I set a root password for MySQL (a non-secure one just for local development), and also creating a database for my project.

At the command line change into your project directory and run

docker-compose up

As this is the first time you have requested the containers this will take a little while and a bunch of stuff will scroll past. As we have a very simplistic configuration here you will see some ‘warning’ messages. Panic not, let it get on with it, eventually you will see it stop.

Assuming all has gone well, you can now go to http://localhost in a browser and you should see the output of the phpinfo() function! That is PHP running inside your container!

If you look back at the command line you should see the log output of your browser visiting the web server.

"GET / HTTP/1.1" 200 23413 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36" 

This is because we have Docker running in the foreground, which is handy when just getting started as you can see the output of the logs. What you will tend to want to do is run Docker in the background as a daemon. To stop Docker type Ctrl-C. Then start it again this time with a -d flag.

docker-compose up -d

You will see the db and web containers start and will then be returned to the command line. You now won’t see any output. To check to see which containers are running type:

docker-compose ps

To stop all containers type:

docker-compose stop

Stop the containers now and we will do some further configuration.

Adding PHP extensions

We have installed MySQL but we need to make sure PHP can communicate with it. To do this we will need to install the PHP PDO extension and MySQL driver.

We can do this using our Dockerfile. Open the file and add a second line:

 RUN docker-php-ext-install pdo pdo_mysql

Save the file. We are running a function, which is part of the official Docker PHP image, to install PHP extensions, choosing the pdo and pdo_mysql extensions.

Run docker-compose up -d --build

This will force Docker to rebuild our containers. Once Docker has completed, you can load http://localhost once again and if you scroll down you should see that we have PDO, and the pdo_mysql driver installed.

Extensions which require Operating System support

PDO simply needs to be installed, however a number of PHP extensions use underlying parts of the Linux operating system. In order to be able to use those extensions we also need to install OS components. If, for example, we try to install the gd extension by adding it to the docker-php-ext-install line, we will get an error in our build.

ERROR: Service 'web' failed to build: The command '/bin/sh -c docker-php-ext-install pdo pdo_mysql gd' returned a non-zero code: 1

If you scroll up when you see an error like this you will see what was happening when the error happened. In the case of gd, it would be when the installer starts checking for the libraries gd needs. We need to install Linux packages, and as we are using a Debian based system we do that using apt.

In the Dockerfile, before we try to load the PHP extensions add the line:

RUN apt-get update -y && apt-get install -y libpng-dev

The package we want to install is libpng-dev. First we run the apt-get update command, then apt-get install with the name of the package or packages we need.

Run docker-compose up -d --build

You will see lots more output as the apt repository is updated and then libpng is installed along with gd. Hopefully the build will now complete successfully. If you look at the phpinfo output at http://localhost you should now have a section about gd showing that it is installed.

You can repeat this process for any extension that failed due to lack of OS support. For example to add curl you will need to add the PHP curl extension plus the curl and libcurl4-openssl-dev packages via apt. Typically, by Googling the last error you get (before the one about the exit code) you will discover which package the machine needs installed. You don’t need to look for Docker specific information - it’s just Linux. Once you know what you need you can add it after in the list to be installed via apt.

Enabling Apache modules

Perch Runway requires that you can rewrite URLs, and most regular Perch sites also use URL rewriting. Therefore you will need to enable mod_rewrite. We do this in the Dockerfile by adding the lines:

RUN a2enmod rewrite
RUN service apache2 restart

We are running the Debian a2enmod command to enable the module rewrite. Then restarting Apache.

All of the commands in the Dockerfile will run when we start our container so our complete webserver Dockerfile now looks like this:

FROM php:7-apache

RUN apt-get update -y && apt-get install -y libpng-dev curl libcurl4-openssl-dev

RUN docker-php-ext-install pdo pdo_mysql gd curl

RUN a2enmod rewrite
RUN service apache2 restart

In English we are saying:

  1. Get the Apache with PHP 7 image
  2. Run apt-get update to update the package libraries
  3. Install libpng-dev, curl, libcurl4-openssl-dev from apt
  4. Install the PHP modules pdo, pdo_mysql, gd and curl
  5. Enable mod_rewrite and restart Apache.

You can now commit the Docker and Docker Compose configuration and anyone else working on the project will be able to use Docker and have the same environment that you are using. If you realise you need an additional module, you can add it to this file, and next time your colleague pulls they will get the code that relies on the new module but also the Dockerfile update adding the module.

Connecting a GUI to MySQL

If you use a MySQL GUI to inspect your databases, or to export the data before deploying it you can connect to MySQL inside your container.

When setting up our MySQL server we mapped the default MySQL port of 3306 to our system 3306 port. We also gave MySQL a root password of ‘docker’, this means that you can connect using the settings:

If you are already running MySQL on your Mac then port 3306 will be in use. In this case map another port to 3306 in docker-compose.yml, for example:

ports: 
      - "4407:3306"

Then when connecting use the port option in your GUI and enter 4407, this will prevent a conflict with the host MySQL server.

Checking out our environment for Runway

As a final test we can check that our environment works with Perch Runway by using the compatibility test. Download the Runway test from the Perch requirements page.

Put the runway test folder inside your www folder.

If your containers are not running, run docker-compose up -d and then visit http://localhost/runwaytest in your browser. You should see the test page and can complete your details.

Runway test
The Runway Test

Note that your database is not on localhost as you might expect but instead you will need to enter db for the location of your database server, as this connects to the linked MySQL container.

After completing your details you should be able to click Next and get a pass! You can then use the same connection details when installing Runway. In your config file your database credentials will end up looking like this after going through the install process:

define('PERCH_DB_USERNAME', 'root');
define('PERCH_DB_PASSWORD', 'docker');
define('PERCH_DB_SERVER', 	"db");
define('PERCH_DB_DATABASE', 'db_runwaytest’);

Grab the code

I’ve added the basic file structure described in this article to our Github repository. You will need to make sure that the paths in the docker-compose.yml are correct for your system and that you have installed Docker.

Discuss this article

I’ve tried to keep this as simple as possible in order that you can get up and running quickly with Docker and start to explore the possibilities for development environments. In future articles I’ll take a look at more ways to configure your environment.

If you have any questions or workflow suggestions to add then post them to the Docker thread on the Perch forum.