Beautiful Laravel Development with Docker & Fig

In my previous post about Docker and Laravel, we needed a large amount of containers to get everything working and to conform to Docker best-practices. These best practices mean we should have one container per process, which helps keep the containers simple and isolated. All the "core" processes (e.g. Nginx, PHP-FPM, etc.) and "development" processes (e.g. composer, artisan etc.) each had their own container. In a production environment this would be perfectly fine, because we would configure the operating system's init daemon (e.g. Systemd) to automatically start the "core" containers on boot, and we wouldn't really need to be running the "development" containers that often so we could deal with the complexity then. A development environment is much more dynamic though, and we're going to need to be starting and stopping containers regularly, each time we run composer, artisan, bower, etc commands. Something a bit more graceful is needed for managing the containers we need to run in our development environment.

Introducing Fig

When you visit the Fig homepage, a slogan greets you stating "Fast, isolated development environments using Docker". This sounds like exactly what we need!

The premise behind Fig is you have a fig.yml file in the root of your application directory which defines how all your containers fit together. You can simply run the fig up command to bring all your containers up and working together. For example, running fig up in a directory with the below fig.yml file in it brings up a PHP-FPM and Nginx web server, linked together.

php:  
  image: dylanlindgren/docker-laravel-phpfpm
  volumes:
    - /laravel:/data
web:  
  image: dylanlindgren/docker-laravel-nginx
  volumes_from:
    - php
  links:
    - php:fpm
  ports:
    - "80:80"

The beauty of Fig is that it works not just on Docker's native environment Linux, but also works with Boot2Docker, and will run the containers within the virtual machine that it creates.

Preparations & Assumptions

I use a late-2013 MacBook Pro, and these instructions are written for that environment. It shouldn't be hard however to translate the below to either Linux or a Windows if you understand the basics of Docker.

To follow these instructions on OS X, you must have Boot2Docker installed with an ISO that supports shared folders. I suggest reading my previous post if you need help with this.

The Boot2Docker virtual machine must have two ports mapped to it, one for web traffic (e.g. 8080), and another for SQL traffic (e.g. 3306). It also must have two shared folders mapped to it, one for your Laravel files and Nginx logs (mounted to /laravel on your Boot2Docker VM), and another for MariaDB's data folder (mounted at /mariadb on your Boot2Docker VM). Here's that structure visualised:

fig.yml

Lets take the 6 containers we used in my previous post, and an additional dylanlindgren/docker-mariadb container, to create a local development environment using Fig:

db:  
  image: dylanlindgren/docker-mariadb
  volumes:
    - /mariadb:/data/mariadb
  ports:
    - "3306:3306"
  privileged: true

data:  
  image: dylanlindgren/docker-laravel-data
  volumes:
    - /laravel:/data
  privileged: true

php:  
  image: dylanlindgren/docker-laravel-phpfpm
  volumes_from:
    - data
  links:
    - db:db
  privileged: true

web:  
  image: dylanlindgren/docker-laravel-nginx
  volumes_from:
    - data
  links:
    - php:fpm
  ports:
    - "8080:80"
  privileged: true

composer:  
  image: dylanlindgren/docker-laravel-composer
  volumes_from:
    - data

artisan:  
  image: dylanlindgren/docker-laravel-artisan
  volumes_from:
    - data
  links:
    - db:db

bower:  
  image: dylanlindgren/docker-laravel-bower
  volumes_from:
    - data

You should notice that each section of the file has a heading, which is the name of the container that will be created (e.g. web). Underneath that heading is all the details about how to create the container, such as the image to be used, which ports to publish, volumes to map, and directive to run the container in privileged mode. Fig supports all commands available under the docker run command. You can read more about fig.yml files at the Fig website.

One thing I want to point out is that the db container has the directory /mariadb on the VM mapped as /data inside it. In a similar fashion, the data container has the /laravel directory on the VM mapped as /data inside of it.

Running our Development Environment

To run Fig, simply run the fig up -d command from the directory where your fig.yml file is. The -d switch directs Fig to run the containers in the background.

When you run docker ps -a you can see all the containers were created.

CONTAINER ID        IMAGE                                          COMMAND                CREATED             STATUS                   PORTS                         NAMES  
654355f20257        41369f0ff493                                   "php artisan dump-au   5 hours ago         Exited (0) 5 hours ago                                 www_artisan_1  
57ff9703c7ca        dylanlindgren/docker-laravel-nginx:latest      "/opt/bin/nginx-star   5 hours ago         Up 5 hours               443/tcp, 0.0.0.0:8080->80/tcp   www_web_1...  
cc289ee65926        dylanlindgren/docker-laravel-phpfpm:latest     "/usr/sbin/php5-fpm    5 hours ago         Up 5 hours               9000/tcp                      www_php_1...  
1c550a761dfb        dylanlindgren/docker-mariadb:latest            "/opt/bin/mariadb-st   5 hours ago         Up 5 hours               0.0.0.0:3306->3306/tcp        www_db_1...  
246e1cb9cf1a        dylanlindgren/docker-laravel-bower:latest      "bower help"           5 hours ago         Exited (0) 5 hours ago                                 www_bower_1  
f0a795b85b03        dylanlindgren/docker-laravel-composer:latest   "composer --help"      5 hours ago         Exited (0) 5 hours ago                                 www_composer_1  
b9f0feed3058        dylanlindgren/docker-laravel-data:latest       "true"                 5 hours ago         Exited (0) 5 hours ago                                 www_data_1  

You'll notice in the fig.yml file we've used the links directive (which translates into the link Docker command) to link the db container when creating the php container. When you use the link Docker command, the details for the container you're linking to become available as environment variables in the container you're running. For example, in the case above, an environment variable in the php container called DB_PORT_3306_TCP_ADDR will exist, as the dylanlindgren/docker-mariadb container publishes port 3306. This environment variable will contain the IP address of the db container. Therefore, in our Laravel database.php, we could use this environment variable as the host, and no matter what the IP address assigned to the db container, Laravel will always point to the correct one!

...
'driver'    => 'mysql',  
'host'      => getenv('DB_PORT_3306_TCP_ADDR'),  
'database'  => 'track',  
'username'  => 'docker',  
'password'  => 'docker',  
...

Running Development Commands

To run one-off commands for development you can use the fig run command. For example, lets setup Laravel with composer.

fig run --rm composer create-project laravel/laravel /data/www --prefer-dist  

This instructs Fig to run the composer container, with the command create-project laravel/laravel /data/www --prefer-dist, and then remove the container after it's run (the --rm switch).

If we wanted to run all the migrations and seed the database with , we would do so in a similar way:

fig run --rm artisan migrate:refresh --seed  

Conclusion

Hopefully this article not only helps you understand how to use Fig to create your Docker + Laravel development environment, but also gives you a greater understanding of Docker and its many advantages.

Feel free to contact me in the comments below, on twitter at @dylanlindgren, or email with dylan.lindgren@gmail.com.