Consider Docker for your Symfony projects
In the recent years many web development companies have invested time and effort into creating Virtual Machine based setups with Vagrant and accompanying tools like Ansible. While a well crafted setup works fine, many have been burned by slow disk access and other issues and have simply returned to a bare metal setup.
However as the complexity of the infrastructure used for your average web development project grows, using a vanilla local environment no longer cuts it. In addition to the standard LAMP components, you've often got a selection of delivery, caching and persistance application to support. Maintaining multiple instances of these supporting tools is impractical.
Docker takes an approach where it wraps a piece of software into something known as a container. These containers are shipped complete with the application code, a runtime, system tools and libraries. Containers are thus guaranteed to always run the same, but can share files and networking with the host and other containers.
With Docker developers can setup development environments, but it's not limited to that as you can also deploy the exact same containers to production environments as well. This makes configuration management simple and coupled to the project code itself, instead of being completely separate.
Quite a few different kind of projects are adopting Docker as a installation method, ranging from eZ Platform to .NET Core to Neo4j. There are plenty of great resources available online about Docker:
- What is Docker and why should I care?
- Docker for PHP Developers (video)
- Create and run Hyper-V containers using Docker on Windows 10 desktop
- Dockerizing Symfony Applications - Symfony Live Berlin 2014
- 9 Critical Decisions for Running Docker in Production
- Docker for Content Management Systems (CMS) Deployment
An example Symfony Docker setup explained
This article does not contain a complete introduction to Docker as here is much more to learn and you're better off reading the official introduction to Docker if you're interested. For a quick practical look at just how one might start using on a Symfony Standard Edition project, assuming you already have Docker installed and have knowledge of server administration basics.
At the root of our project is the file docker-compose.yml, which describes the containers required to run an individual project. The file is used by a Docker Compose, which is a tool for defining and running applications with multiple containers. The file's structure is quite straightforward, but there are plenty of options which are described in the documentation.
We will be installing a rather standard PHP application with some extras like Redis and Varnish. Our compose file starts off with a version definition, that is only required because there are two completely separate versions of the docker-compose.yml specification itself:
This is followed by a full list of services (containers) with configuration options, beginning with the Nginx web server:
services: nginx: image: nginx ports: - 8086:80 volumes: - ./:/var/www/symfony - ./docker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf links: - fpm - varnish
In the nginx service definition we define the application image to be used. This refers to an account on Docker Hub, a central repository where application images are stored. In our case the nginx repository is the official one. Docker images can be created by anyone and depending on images that are not maintained is risky, this is why choosing an official image is often the most reliable choice.
In the ports section we define the internal port 80 of the Nginx container to be mapped to 8086 outside of the application. This means that you will be able to access the Nginx web server on your localhost port 8086. If you want to simulate load balancing using more web servers, you could simply add more nginx instances with different ports
The volumes section we define what files and directories are shared between the host machine and individual containers. In our example the root of our project is mapped to where you might normally expect the document root to be in Linux environments. In addition to the document root, the configuration file is mapped to be used as the configuration file. The file docker/nginx/nginx.conf itself is obviously a regular nginx configuration file containing the rewrites for Symfony (recommended Symfony Nginx configuration).
Next is up are links to other containers, which express dependencies between services, used for determining the order of individual container startup and so on. One of the linked containers is the PHP-FPM that we will used to execute our Symfony application with PHP 7.
For main data persistance we will use a MySQL database:
mysql: image: mysql environment: MYSQL_DATABASE: devdb MYSQL_USER: devdb MYSQL_PASSWORD: devdb MYSQL_ROOT_PASSWORD: volumes: - ./docker/mysql/data:/var/lib/mysql
In the the container definition image is set to be the official Docker image from MySQL, again a very safe option. In the environment section we pass the basic information required for running a MySQL database. These environment variables are used by the image itself. The usage of variables are defined by the image vendor, in this case Oracle.
For volumes we map the MySQL data directory to our local project directory, but you might want to place it elsewhere where it might not be deleted so easily. Our Docker image will obviously ship without any initial database content, so you'll need to have a data migration approach in place to synchronize database structure and content, etc.
For HTTP caching we will add Varnish, a reverse proxy defined as follows:
varnish: image: newsdev/varnish:4.1.0 environment: VARNISH_PORT: 8087 VARNISH_MEMORY: 1G ports: - 8087:8087
All the configuration options for image, environment and ports. Are familiar to us by now, but what is different is the source and version of the image. For Varnish, there is currently no official Docker Hub account so the image we use comes from the New York Times development team.
In addition we also define the exact version number to be 4.1.0, where as in our previous definitions we've taken the default version from the repository. Next we setup Redis, a simple key-value store used for caching:
redis: image: redis:latest
Without the need for data persistence a very simplistic configuration with the official image and no shared volumes. Configuration values will be default ones. Finally we will add our last piece, PHP to actually execute our Symfony application:
fpm: build: docker/php-fpm/ links: - redis - mysql volumes: - ./:/var/www/symfony - ./docker/php-fpm/php-ini-overrides.ini:/usr/local/etc/php/conf.d/99-overrides.ini
In the above configuration links and volumes we already familiar with, but build is something new (in the past we used image). Build allows us to take an existing image and make changes to it, our example Dockerfile for building our custom image is as follows:
FROM php:fpm RUN docker-php-ext-enable opcache
In a docker file we choose the base image to be the official PHP docker image. We then just enable the opcache extension, but you can use the Dockerfile to script any commands to build an image. More complex Dockerfile creation is out of the scope of this article, but the official documentation has great resources and best practises for creating Dockerfiles.
To start the project you need to run docker-composer up:
Reggan:docker.sf janit$ docker-compose up Pulling varnish (newsdev/varnish:4.1.0)... 4.1.0: Pulling from newsdev/varnish d4bce7fd68df: Pulling fs layer d4bce7fd68df: Downloading [==> ] 2.096 MB/51.35 MB a3ed95caeb02: Download complete d444db50e532: Download complete 313b9a4ae98d: Waiting 9ee155cd21b7: Waiting
This will download all required images, run the build process and finally start your project with all the associated containers. To see which containers are running with the docker ps command:
-Reggan:~ janit$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9c090de4060d nginx "nginx -g 'daemon off" About a minute ago Up 4 seconds 443/tcp, 0.0.0.0:8086->80/tcp dockersf_nginx_1 457c063786d0 dockersf_fpm "php-fpm" About a minute ago Up 5 seconds 9000/tcp dockersf_fpm_1 b9d5913e2ab7 redis:latest "docker-entrypoint.sh" About a minute ago Up 7 seconds 6379/tcp dockersf_redis_1 6f78908fe305 mysql
If you want to try running the project for yourself, the example is available on Github: https://github.com/janit/docker.sf