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 use0.0.0.0:8081
and can domyawesomeproject.local
Installing Docker for mac
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
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
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.
How do I stop the running services?
docker-compose down — remove-orphans
How do I force a rebuild?
docker-compose build