A Story of Laravel, Docker, Xdebug and a Mac

Some weeks ago I decided to ditch the old Mamp Pro that has servantly been part of my development ecosystem for so many years and try one of those fancy alternatives, Docker. I liked the idea of not depending on my local setup to run the projects I work on. Several times before, I encountered issues with different database engines, php version, too many or too few extensions, folder permissions, etc. This seems like a solution to all this.

In another post, I cover how to get local domains working with multiple compose files and all without breaking xdebug or losing your mind, so you don’t have to use 0.0.0.0:8081 and can do myawesomeproject.local

Installing Docker for mac

Docker community logo

This is the most straightforward part of the process, we go to docker’s website, download the app, and install it. Why am I writing this in its own paragraph?. There’s another app for mac though! , and it’s the wrong one, so let me just link you guys to the correct one.

Ok, I have the app, what now?

Creating a compose file and a Dockerfile

Our project will be running from something called a compose file, which is a yml definition of the services that will make up our machine. Those services come from the docker repository and they cover pretty much anything we can think of.

There are repositories you can use as a base that are already made for laravel but I chose to use the php repository, specifically php:7.1-apache which will allow me to further customise my environment.

When you define your compose file, the services defined on the yml file will come from images, either directly or built from a Dockerfile script. In our case, we’ll be defining two services, the database which will run mariadb and the httpd one which will run apache.

For the database one we only need to specify some configuration but a stock image will be all we need. For the server one though, we need the above-mentioned dockerfile.

docker-compose.yml

I don’t want to go over this line by line since once you have the example is very easy to locate the pertinent documentation to learn more about it. I added little comments to the file for my own sanity.

You can specify a custom name for a container by using the property container_name however, it’s better to let the name be autogenerated and use the container folder as the custom part. For instance, in this example the folder holding the compose file is named dock and the containers when launched are named dock_httpd_1 and dock_db_1. You know what to do from here.

Depending on your docker engine version, the version of the compose file below will need to be changed. This, and other really useful information can be found on the Compose File Reference
#This file is inside a folder named 'dock' at the root of my laravel project
version: "3.1"
services:
#PHP with apache
httpd:
#image will be built by processing the Dockerfile in this path
build: .
links:
- db:db
#map ports between host and the service
ports:
- 8180:80
- 1080:1080
#map host folders (relative to this file) to service
volumes:
- ../public/:/var/www/html
- ../:/var/www
expose:
- "8080"
#mark the db services as a dependency
depends_on:
[db]
#give an alias to this, to refer to it without knowing ip
networks:
report:
aliases:
- web
#maria db
db:
image:
mariadb
restart: always
#storing the mysql folder outside allows persistence
#of databases between executions
volumes:
- ../dock-mysql/:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD:
root
MYSQL_USER: test
MYSQL_PASSWORD: test
MYSQL_DATABASE: test
ports:
- 3307:3306
expose:
- "3307"
networks:
report:
aliases:
- database
volumes:
db_data:
networks:
report:
driver:
bridge

Dockerfile

Essentially, specify which repository it will use as a base for the service, then run all the commands we need to customise it to the particulars of our project. I needed certain libraries and of course that includes Xdebug.

We could have more than one Dockerfile, to build our images, in this case it’s a stock mariadb image and the one built on top of the php-apache one.

FROM php:7.1-apache
ENV PHP_IDE_CONFIG="serverName=localhost"

RUN a2enmod rewrite

# Install developer dependencies
RUN apt-get update -yqq && apt-get install -y git libsqlite3-dev nano libxml2-dev libicu-dev libfreetype6-dev libmcrypt-dev libjpeg62-turbo-dev libpng12-dev libcurl4-gnutls-dev libbz2-dev libssl-dev -yqq

# Install xdebug for development
RUN pecl install xdebug \
&& docker-php-ext-enable xdebug

#Install other php extensions
RUN docker-php-ext-install sockets \
&& docker-php-ext-install mysqli \
&& docker-php-ext-install pdo_mysql \
&& docker-php-ext-install json \
&& docker-php-ext-install xml \
&& docker-php-ext-install zip \
&& docker-php-ext-install bz2 \
&& docker-php-ext-install mbstring \
&& docker-php-ext-install mcrypt \
&& docker-php-ext-install curl

RUN docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/
RUN
docker-php-ext-install gd

# Copy the configuration file into xdebug, if running phpinfo() you see the loaded file is not this one, change the path accordingly.
COPY xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

# install composer
RUN curl --silent --show-error https://getcomposer.org/installer | php

WORKDIR /var/www

xdebug.ini

You can see that on the Dockerfile we have a command that will copy a file named xdebug.ini to a certain folder. That folder is where the configuration for xdebug is pulled from, I did leave a comment on the Dockerfile but let me insist, since this took me a long time to figure out. If it’s not working make sure you’re loading the correct configuration file for xdebug, different servers and versions will use different folders, so it’s always a good idea to double-check.

zend_extension=xdebug.so
xdebug.remote_host=10.254.254.254
xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.idekey=docker

Wait, what is 10.254.254.254?

If you’re reading the previous file and wondering if you should replace that with your actual host IP. No. What you need to do is set your docker environment’s IP to that one. There are several methods listed on the internet, including one where you’d use a special Mac-only DNS thingy docker.for.mac.localhost but I didn’t manage to get this to work.

What we are going to do instead is follow the instructions from here which are in fact just running the following command and rebooting.

sudo curl -o /Library/LaunchDaemons/com.ralphschindler.docker_10254_alias.plist https://gist.githubusercontent.com/ralphschindler/535dc5916ccbd06f53c1b0ee5a868c93/raw/com.ralphschindler.docker_10254_alias.plist

Building the beast

sample folder structure

Create a dock-mysql folder at the same level as the one holding dock, feel free to rename this to anything you like as long as you update the other files to reflect it.

The name you give to the folder will be prepended to the name of the running service

Making sure you have docker for mac running, you need to navigate to the folder we created which contains the three files above. Once there you need to execute docker-compose build which will attempt to build the services from the yml definition.

After this, we should be ready to try to launch the services, sorry if I’m naming stuff incorrectly. Running docker-compose up -d will launch them, the -d flag specifies that we want to run them in a detached mode that returns the names of the newly launched services, a service is an image that runs in a container.


Accessing the services

Another thing we need to get our heads wrapped around is the fact that we no longer navigate to a folder in our system to run commands, we need to do enter the machine that’s running the service to do that. It’s pretty simple and convenient.

First, we need to get a list of the services that are running in order to get the id of the one we want to access. Do this running docker ps

sample output

Say we want to connect to the apache service to run some artisan command for our laravel project like running migrations with seed. We connect by running docker exec -it ae40da4193ef /bin/bash where ae40da4193ef is the id of the container.

Once inside we need to navigate to the right folder, which contains our project and run the command as we would normally.


Housekeeping and useful stuff to know

After this, all you need is to configure your ide to listen for connections and your browser to send the correct tag which we specified in the xdebug.ini file.

Yay! we have breakpoints!

How do I stop the running services?

docker-compose down — remove-orphans

How do I force a rebuild?

docker-compose build

Like what you read? Give Juan Cortés a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.