A Docker development environment for a Symfony applicationCaendra TechBlockedUnblockFollowFollowingJun 18Photo by chuttersnap on Unsplash— by Eugenio Carocci, Web Developer at Caendra Inc.
For every software development team, there comes a time when the need arises to migrate a Vagrant development environment to a much cooler, faster, and exciting one, based on Docker.
In this article, we will talk about the experience we acquired during this journey, as well as the pros and cons comparison regarding the choices we made.
Before we start diving into the theory and all the steps to get there, here you can find a git repository containing a sample Symfony 3.
4 application with a gorgeous development environment we made completely with Docker Compose.
To give it a try, you need to:Clone the git repo.
Follow the very brief README file in order to get the development environment up & running.
Open a browser, visit http://www.
com and get the following:Symfony 3.
4 welcome pageNow that you have seen the full potential of this approach let’s dive a little bit deeper into the various components and technologies we used.
Vagrant, Docker and Docker Compose overviewIn this section, we will provide a brief overview of these technologies.
Vagrant“Vagrant is a tool for building and managing virtual machine environments in a single workflow.
With an easy-to-use workflow and focus on automation, Vagrant lowers development environment setup time, increases production parity, and makes the “works on my machine” excuse a relic of the past.
”It seems to be a great solution to create a portable development environment completely independent of the developers’ personal computer.
And yes, that’s what we believed.
However, Vagrant has a problem.
Its problem is that for each project you need a complete virtual machine which leads to high consumption of resources on the host machine.
Since we are not living in a world where developers’ machines have unlimited resources, in some cases there might be performance issues.
If you would like to acquire more extensive knowledge about this technology, you should read Vagrant’s official documentation.
Docker“Docker containers wrap up software and its dependencies into a standardized unit for software development that includes everything it needs to run: code, runtime, system tools and libraries.
This guarantees that your application will always run the same and makes collaboration as simple as sharing a container image.
”As opposed to Vagrant’s approach, in this case, the main idea is to have a container (or, as we will see, a set of them) and not a whole virtual machine for each project.
Since containers are extremely fast and lightweight, we get the added benefit of resource savings, greater flexibility and a quite long list of other qualities; containers also seem to be a brilliant alternative when wanting to create a development environment, but they are not limited to just that of course.
There are a lot of interesting articles about Docker and containers like the following one: A Beginner-Friendly Introduction to Containers, VM and Docker.
Docker Compose“Compose is a tool for defining and running multi-container Docker applications.
With Compose, you use a YAML file to configure your application’s services.
Then, with a single command, you create and start all the services from your configuration.
”From the above description, it is evident that this technology provides the means to create an architecture with several components easily or, to say it more correctly, a set of containers.
Docker Compose provides a set of useful commands that allows the user to start, stop and retrieve information about the defined services.
The structure of the application is defined in a file named docker-compose.
The information provided in this file is the one needed to build the application environment.
Development environment architectureIn our case, we needed to build a development environment in order to create a Symfony application.
The main elements of the architecture we used were:An Apache web server able to process requests issued from a browser on the host machine.
An element containing: PHP-FPM, Composer, Node.
js and Yarn.
A MySQL database to store our application data.
An element with phpMyAdmin able to connect to the MySQL DB.
Building the environment with Docker ComposeDocker Compose logo from https://github.
com/docker/composeThe crucial point of our solution is the environment definition through the creation of a Docker Compose configuration file.
Here you can take a look at our docker-compose.
yml file based on our specific needs:Since we wanted all the newly available features of Docker Compose, we decided to adopt version 3.
7 as you can note from the very first line.
In the following, we are going to provide more details about the most interesting parts of the file, and in addition to that, we will briefly discuss the use of environment variables to create a flexible environment.
Services sectionThis is the main part where all the needed components of the development environment are defined.
In our case, the following services have been defined:php-fpmwwwmysqlphpmyadminhost-managerThese elements are exactly the ones we previously mentioned, except for the last one, which will be analyzed later.
There is a fundamental distinction to be made among the defined services: some of them use an out-of-the-box image retrieved from Docker Hub, the public Docker registry, whereas other ones use a custom image.
In particular, both php-fpm and www are custom images that extend a third one called base.
The reason behind this distinction will be argued later in this article.
Each one of these services holds a set of property that describes its behavior.
For instance, it is possible to define:How many replicas have to be present.
Which ports have to be exposed.
Which volumes are present in the container and many other things.
If you want to deep dive on this area you should check out the official Docker Compose documentation.
Networks sectionThis section is used to define the network where all the services will be reachable.
In our case the network name chosen is symfony-docker-dev.
The subnet definition was done using the PROJECT_SUBNET environment variable and for this reason, it is highly flexible to suit your needs.
Each element of the architecture under the networks key has an alias.
This one is the hostname that can be used by all the other members of the network in order to reach it.
For instance, the phpmyadmin container will be able to communicate with the mysql one.
Environment VariablesCompose supports declaring default environment variables in a file named .
env placed in the folder where the docker-compose command is executed.
This file holds all the variables that make the solution flexible and reusable even in different projects hosted on the same machine.
Below we see what our environment variable file looks like:Within the file, there is a crucial environment variable called COMPOSE_PROJECT_NAME.
This variable main use is to provide a namespace for all the containers defined for a specific project, thus it is possible to create multiple decoupled development environments by properly setting this variable.
One use case could be, on one side, an experiment on new functionality and on the other one keeping the current behavior of the application.
Creation of custom imagesIn this section, we’ll show how we made Dockerfiles for the following images:basephp-fpmwwwCreation of a Dockerfile for base imageThis image has been created extending the official Centos 7.
Inside:EPEL and IUS package repositories are configured.
Several packages are installed.
Below is the base image Dockerfile:Creation of a Dockerfile for PHP-FPM imageThis image has been created extending the base image.
1 with a set of extensions are installed.
Configuration files are copied from our project directory.
A user and a group are added.
js and Yarn are installed.
The default command of the image is php-fpm.
Below is the php-fpm image Dockerfile:Creation of a Dockerfile for www imageThis image has been created extending the base image.
Inside:An Apache 2.
4 is installed.
Configuration files have been updated.
A user and a group have been added.
The default command of the image is httpd.
Below is the www image Dockerfile:Design strategiesDuring the construction phase of our new development environment, we faced a lot of interesting challenges.
Thanks to them, we had the opportunity to learn a lot of cool stuff.
In the next part you will find a list containing some advises that we hope can help you through your journey.
Extensive usage of the .
env fileIt is relevant in this context that the final result is extremely configurable; this can be achieved using environment variables extensively that can be edited based upon developer needs.
In our scenario, we decided to version only the .
This one is a copy of .
env that can be distributed without any problem since it does not contain any sensitive information.
Every developer will create their own .
env, starting from the .
dist and filling in secrets if they come up.
Executing the following command inside the folder where the project is stored, it’s possible to see the final result that will be obtained replacing in docker-compose.
yml all the environment variable values.
$ docker-compose configCreate a base imageBased on our needs, we chose CentOS as the operative system running both Apache and PHP-FPM.
It’s exactly for that reason that we decided to create a base image using this particular OS.
Starting from this base image, we created one image for the www service and one for php-fpm.
This gave us much more control on what is present inside these images, beginning with PHP repositories up to the version of the Apache web server.
This approach, as opposed to the usage of out-of-the-box images, required a greater effort since we had to configure everything accurately.
But, on the other hand, it gave us the advantage to obtain a development environment very similar to the production one.
Container bootstrapWe noticed that a lot of the out-of-the-box images are highly configurable so you should definitely check them before starting something custom.
For instance, using the MySQL image it is possible to mount a folder containing SQL files that automatically run when the container starts.
In this article, this particular case is explained in a clear and detailed way.
Share SSH keys with containersWithin our project, we needed to access some private repositories from the php-fpm container when using the Composer command.
In order to access these repositories, we wanted to use the SSH keys present in the host machine.
For this reason, we had to find the best way to share them with the container in the easiest and safest manner possible.
Different articles try to tackle this problem and several approaches are proposed.
In our case, we decided to add the SSH keys into the SSH agent in the host machine and after that, we shared the agent socket file with the container.
To achieve our goal we mounted a volume, in the php-fpm container, sharing the SSH agent socket file present on the host machine.
In order to let the container know where to find the agent socket file an environment variable with the path was defined.
Expose containers to host machine with their namesIn order to access the web server from the host machine in the easiest way, one approach might be to add in the hosts file of the host machine the pair (IP, host-name) of the www container.
Sadly, this solution has a problem; the container’s IP address could change from one execution to another, and for this reason, it needs a manual update of the file each time.
In addition to that, every developer in the team has to update his host file manually.
Clearly, this is not what we want.
We found that an interesting image called docker-hostmanager exists, and it cares about the updating of the hosts file on the host machine.
On the project’s page, there are all the required actions needed to let the container work properly on MacOS, Windows and Linux.
In the interest of letting the whole solution work correctly, the expose property is needed on all the services that have to be reachable by their names from the host machine.
In our case we added this configuration option to the following services:www;phpmyadmin.
The final result in the hosts file of the host machine will be one row for each service exposed in the following format:<container-alias>.
com <container-ip>Thanks for your time.
We hope that this article helps some of you in the creation of a great Docker development environment.
Let us know what you think with a comment!.