Developing Symfony applications with Docker series part I: Getting started

In this series I’m gonna share all that I’ve learned while switching from a Vagrant powered environment – running all required software in a single VirtualBox instance – to a Dockerized setup where every process runs in a separate container. But what exactly is Docker? From the Docker site:

Docker containers wrap up a piece of software in a complete filesystem that contains everything it needs to run: code, runtime, system tools, system libraries – anything you can install on a server. This guarantees that it will always run the same, regardless of the environment it is running in.

Now that sounds great, doesn’t it? As a matter of fact it does, but you have to get a grip on the concept before it starts paying off. In these blog post series I’ll show you how to create a multi container Symfony application and how to get the full potential out of it. For now I’ll only focus on using Docker as develop environment. Perhaps a new series for Docker in production will follow in the near future :).

I’m a Mac OS X user so some problems I describe are related to the fact that I have to use a virtualisation layer to use Docker. If you’re happen to be on Linux, you can just skip those sections.

Installation

VirtualBox is required to run a Linux virtual machine so make sure you have a recent version installed. On the Docker site follow the installation instructions. When you’re done you should have docker, docker-compose and docker-machine binary available to you.

Create Linux virtual box

With docker-machine it’s fairly easy to create and manage a virtual machine for running Docker in. Let’s create a new instance:

If you need a box with more memory (this can happen when you have a lot of containers) you can create one with more RAM with --virtualbox-memory "2048". You’ll receive a no space left on device error when that happens.

All left to do is setting correct environment variables so Docker daemon knows how to connect our box:

Now let’s try to see if it works by listing the containers:

Create Symfony project

Now we’re ready to create a new Symfony project:

Configuring docker-compose

Docker-compose reads its configuration from a docker-compose.yml file, so create a empty one in the root of your shiny new project.

You should end up with a directory structure like this:

Install php and nginx

We’re almost there, so hang on. Obviously we’re gonna need php and a webserver so let’s install php-fpm and nginx. Never reinvent the wheel, so when you need a service containerized, always search it on Docker hub. As with bundles: there’s a container for that. We’ll use the official php and nginx images for now.

Heads up: When adding more images to your configuration take note from which image they derive. Most images extends from debian:jessie, which you probably want for your images. Docker works with a layered file system, so if all your images derive from the same parent, that will speed up the build process and also consume less space (also during transfer!).

Edit your docker-compose.yml like this:

The root element is the name of the container, you can pick whatever you like. I always try to keep these short so it’s easier (less typing) when running commands against a specific container.
The image field tells docker-compose which image we want to use for our container. The ports field allows us to expose ports on the container and forward port(s) from the container to the host, so we actually connect to the container. The value "8080:80" means we’re exposing port 80 on the container and forward it to port 8080 on the host.

You should start the containers now:

Docker will pull the images from the registry, build and start them. The -d flags tells Docker to run the containers daemonized in the background. I’ll get back on that later. When it’s done, verify if they’re both up and running:

Connecting to the box

We’ve forwarded port 8080 to our host, but connecting to localhost:8080 doesn’t work (you did try the link, didn’t you? :)). Because Docker runs in a virtual machine, we need to figure out its IP so we can connect it. Of course this isn’t very difficult:

Let’s try that IP on port 8080 and you’ll see it works: http://192.168.99.100:8080/. You’ll want to add an entry for that IP in your /etc/hosts file. Let’s pick symfony3.dev for now.

As you’ve probably discovered by now, we’re presented the default nginx page and not our shiny new Symfony application. To fix this we have to link the php container to the nginx container so they can communicate with each other. The php-fpm container needs access to our project’s php files in order to parse and serve them. Also, the nginx container requires a nginx configuration. We have to alter the Docker image and for that we need a Dockerfile.

Custom Dockerfile

The Dockerfile represents every step to be taken before the container is ready to use. Normally you would use a configuration management tool (Ansible, Puppet, Chef) to accomplish this, but in Docker you manage this via the Dockerfile.

It’s import to know that each line should contain one step. Each line creates a new layer and the number of layers is limited. One logical step per line improves the caching mechanism. For more information regarding this topic, refer to the best practices.

To configure nginx we’re going to use the nginx configuration supplied by the Symfony team. We have to copy it into the container. Create a new directory docker/nginx in the project root and add the following Dockerfile:

Create a symfony3.conf file in that docker/nginx directory as well and fill it with the following configuration:

In case you haven’t you should add symfony3.dev to your /etc/hosts file with the IP from docker-machine ip docker-tutorial.

Now let’s put it all together and update our docker-compose.yml accordingly:

Take note of the changes we’ve applied: image under nginx is replaced with build: docker/nginx which refers to the directory where the Dockerfile resides. The nginx container has a links key where we link it to the php container. Both containers have a volumes key where we mount the current directory into the container under /app path. This way the container has access to the project files.

Stop all containers and build them:

Then start them again:

When you visit http://symfony3.dev:8080/app_dev.php in your browser, you’ll see the You are not allowed to access this file. Check app_dev.php for more information. message. Remove the access check from app_dev.php and try again.

Unfortunately another well known error pops up: Failed to write cache file “/app/var/cache/dev/classes.php”..

Permissions

In my opinion the best solution to this problem is to run the console commands and php-fpm process under the same user. Without any modifications, the console commands are run under root and the php-fpm process runs under www-data. To accomplish this we also have to use a Dockerfile for the php container.

Again, stop all containers:

Create a new directory php-fpm under the docker directory. Add the following Dockerfile:

Also, add the following php-fpm.conf file:

Because I suck at naming new users I just use vagrant as my development user. Think of it as a tribute to vagrant :). The docker directory tree should be:

Now build and run the containers:

If you visit http://symfony3.dev:8080/app_dev.php now, you’ll see the Symfony welcome pages smileys at you. With this “hello world” for Symfony working we end this first post.

Next post I’ll show you how to speed up things if you’re on a Mac (the default Symfony app takes ~2000 ms to load in the current situation). Also, I’ll show you the possibilities to store your data when working with containers.

Slow initialization time with Symfony2 on vagrant

A few days ago we switched our complete infrastructure from hosting provider. Also we made the switch from CentOS to Debian. So we also got a new fresh development environment using Debian and Vagrant (and latest PHP and MySQL ofcourse :)).

We expected the new dev box to be fast, but the oppositie was happening: it was slow as hell. And when I mean slow as hell, it’s terribly slow (10 – 20 seconds, also for the debug toolbar). In the past we had some more problems with performance on VirtualBox and Vagrant. There are some great post out there on this subject (here and here) which we already applied to our setup. In a nutshell:

  • change logs and cache dir in AppKernel
  • use NFS share

The cause: JMSDiExtraBundle

After some profiling I discovered there were so many calls originating from JMSDiExtraBundle I tried to disable the bundle. And guess what: loading time dropped to some whopping 200ms!

The real problem was the way the bundle was configured:

This causes the bundle to search trough all your php files in those locations. Apparently in the old situation (php 5.3 and CentOS) this wasn’t as problematic as in the new situation (php-fpm 5.5, Debian).

How to create a VM with PHP 5.4 using Vagrant and Puppet

Everybody PHP developer who didn’t live under a rock the past few months must have heard of the upcoming release of PHP 5.4. Well, March 1 it was finally there: the official release of PHP 5.4!

Because it definately will take some time before we can install it with our favorite package manager, I decided to create a small Puppet manifest in combination with Vagrant that will build a virtual machine. Normally, you have to compile PHP from source in order to try it that quickly after it has been released. However, the nice dudes from dotdeb.org compiled them already for us, and provide it via their repository. Nice! 🙂
Furthermore, Vagrant provides us a cool Ubuntu server image, ready to rock with Puppet. So, let’s get thing of the ground shall we? (pro tip: scroll all the way down to simply clone my git repository with all the code ;))

Prerequisites

In order to get things running smoothly you have:

  1. Installed VirtualBox 4.1.x
  2. Installed Vagrant
  3. Some IDE for editing Puppet manifests (I prefer Geppetto)

Creating our project structure

Let’s start with creating a basic directory structure for storing our files needed. Fire up Eclipse/Geppetto and start a new project in your workspace. Create the following structure:

  • manifests
  • modules
    • php54
      • files
      • manifests
  • www

Writing the Puppet manifest

There are a few things we need to accomplish with Puppet, in chronological order:

  1. Add the dotdeb.org repository to/etc/apt/sources.list
  2. Add the dotdeb.org GPG key
  3. Run apt-get update
  4. Run apt-get install php5

Because we can bucket files to the VM easily with Puppet, I choose to supply a modified sources.list so Puppet takes care of copying it into the VM. Then, I download the GPG key with the famous wget utility and pipe it into apt-key. The exec call to apt-get update speaks for itself, and last but not least I tell Puppet to install the latest php5 package.

With the require directive I make sure that all commands are executed in the right order.

The contents of the init.pp file in the php54 module looks like this:

Also we create a sources.list file in the “files” directory (you could change the Debian mirrors):

Last thing I do is create the entry point for Puppet, namely the site.pp file in the manifests directory:

All I do is including the php54 module which will handle all the magic for us.

Creating the virtual machine

Now Vagrant comes in to use. Create a Vagrantfile in your project root with the following content:

I’m using a Debian Squeeze box from vagrantbox.es here, credits go to the original author. I’m making use of the VirtualBox shared folders. These are not really fast, but will do for testing purposes. If you want some more advanced sharing I suggest NFS or Samba if you are on Windows.

Now, all left to do is start the VM. Open up a terminal and do vagrant up in your project root:

Navigate to http://33.33.33.10 with your favorite browser and have some happy testing 🙂

For all the lazy people out there, you can start the box with just 3 commands:

Creating a CentOS 6.2 base box for Vagrant

One of the cool things I stumbled upon last year at the Dutch PHP Conference was Vagrant. After some little experimenting I was convinced: this is the right tool for our development environment!

Since we’re running CentOS at the web agency I work for, I soon started searching for a nice base box to build upon. Not satisfied by the boxes available, I decided to create a base box myself.
Today we decided to switch to CentOS 6 for all our new boxes, so I had to build a new image for our developers to build on with Puppet and Vagrant. Since I had this free hosting account from Combell sponsored at PHP Benelux Conference I thought it would be nice to give something back to “the community” by writing my first blog post :).

This tutorial assumes you have installed Virtual Box. First of all, we start with downloading an ISO image so we can install a fresh instance of CentOS. Pick a mirror nearby and download the right image. We’ll be using the netinstall ISO since we want to keep the size of the image as small as possible.
I hear you thinking: why doesn’t he use the minimal ISO if size matters? Believe me, the minimal is *really* minimal. Too minimal is you ask me!

While the ISO is downloading, let’s fire up Virtual Box and create a new virtual machine. Choose the name you want and set OS to “Linux” and version to “Red Hat”. Also create a virtual disk with the desired space and pick “Dynamically allocated”.
Once you’re done with creating the VM, don’t forget to disable audio and USB. Also make sure you set the base memory to something like 700 MB. Otherwise the GUI installer won’t work, and you get the text installer which is limited!

Installation

Next thing is to fire up the VM, and Virtual Box’s “First Run Wizard” will pop up. Pick the ISO you just downloaded and click “Start”. After it’s booted, choose the option for installation and hit “return”. If all went fine, the installer will pop up. A few things to keep in mind here:

  • disable ipv6
  • select HTTP installation method and enter a mirror nearby; for using the Dutch Leaseweb mirror like I did you enter “http://mirror.nl.leaseweb.net/centos/6.2/os/i386/” (just replace the hostname with your preferred mirror’s hostname)

CentOS netinstall mirror

After the kernel is downloaded, you’ll see the GUI installer.
Follow the wizard and select partition layout (I use the default settings).
A few important things:

  • Set vagrant as the root password
  • Set vagrant-centos62 as hostname (Vagrant conventions)
  • In the software selection window make sure you choose minimal as the set, and also choose “Customize now” at the software selection:

Software selection screen

In the next window unselect all packages (only one is selected if I remember correctly). After that you’re done, and the wizard will start downloading and installing the box.

Once it’s done you’ll be prompted to reboot. Before rebooting, make sure you remove the netinstall ISO as CD attachment (in the “storage” settings). Also, to make things more easy during the configuration of our box forward the SSH port like this (select “Network,” “Adapter 1,” and then “advanced settings” and select “port forwarding”):
Port forwading settings Virtual Box
Now boot the VM (don’t forget to enjoy the new animated boot screen ;)).

Configuration for Vagrant

Once booted, connect to your VM via SSH:

Since there’s barely anything on the machine right now, I start with installing my favorite editor and some other stuff we’ll need:

Next we are going to install the VirtualBox Guest Additions. Click on your VirtualBox window and select “Devices” and “Install Guest Additions”. The option is in the VM window, and not in the VirtualBox control panel. Install them like this (ignore the erros you get, this is because we aren’t running any fancy GUI):

Because we’ll be provosioning the VM with Puppet, we start with downloading the EPEL RPM package:

Add it:

Verify with:

Then install Puppet:

I personally prefer installing Puppet with yum, but you could also install it via gems or any of the other methods on the official installation guide. Installing with yum auto resolves dependencies, and with CentOS 6 we don’t have an ancient Ruby version anymore ;).

In order to keep things speedy, add the following line to the /etc/ssh/sshd_config file (it will disable DNS lookups):

Add vagrant user and set permissions

We’re almost there. Only thing left to do is add the vagrant user so Vagrant can log in and build our box.
Start with creating the user and adding it to the “admin” group (set the password to vagrant as stated on the Vagrant base box documentation):

Now we only have to make some changes to the sudoers file. Do this with visudo (or manually edit /etc/sudoers, discouraged):

There are a couple of things that need to be changed:

  • Add SSH_AUTH_SOCK to the env_keep option
  • Find the line with Defaults requiretty and disable it by placing a # in front
  • Add the line %admin ALL=NOPASSWD: ALL so that the vagrant user can sudo without password

Last but not least we’re going to add the public key so Vagrant can easily SSH into our box. Login with the vagrant user:

Please note that I’m using the public insecure pair as described on the readme. If you’re not planning to share the box you probably want to use the config.ssh.private_key_path option in your Vagrantfile.

Package the box

Now first let’s clean up:

Shutdown the box and package it. Replace centos62-32 with the name of your VM:

Optionally you can also add a Vagrantfile into your base box.