Docker is currently one of the hottest technologies around, because it solves a very specific problem: the ability to easily package and deploy a (self contained) application, without the overhead of traditional virtualization solutions.

In this post you’ll learn how to build, run and host Docker containers, integrate with other containers, and see how Vagrant interacts with Docker.

Background

The Linux kernel contains a number of containment features that enable resource (CPU, network, memory, etc.) & process (user id’s, file systems, process trees) isolation on the same host, without the need for a virtual machine: cgroups and namespaces.

This operating system-level virtualization imposes far less overhead. User space instances (also called containers) run within the host OS, so a hypervisor and guest OS are not needed. This results in lightweight images and incredibly fast start/boot times: think seconds, not minutes. Containers are like vm’s, without the associated weight.

Docker is a toolkit built around those containment features. Before version 0.9, Docker relied on LXC (LinuX Containers): a (userspace) interface to the kernel containment features. As of version 0.9, Docker has its own interface layer called libcontainer.

Installing Docker

Installing Docker is easy if you are using Ubuntu. To install the official package (may not be the absolute latest version):

Note: because Docker relies on Linux-specific kernel features, it is not natively supported on OSX or Windows.
There are some workarounds: use Vagrant (see below), or refer to the Docker installation instructions ( https://docs.docker.com/installation/windows/ and https://docs.docker.com/installation/mac/) for details.

Creating and starting a container

Let’s start with a small PHP app, which we will package and deploy as a Docker container. The app will expose a simple socket server on a port (1337), and output a string to connections on that port. To build the app we’ll be using React, a library that adds event-driven, non-blocking IO to PHP.

First, install composer (http://getcomposer.org), then run the following command:

The container will be named “our_app_container”, and will run in the background (the ‘-d’ option). Docker will also proxy port 1337 on the host to port 1337 in the container. After starting the container, ‘docker ps’ shows the list of running containers and their attributes, something like:

Now, how to add a Redis server to our app? We can connect directly to a specific hostname, or assume that whoever works on our app has Redis installed and running. Both are somewhat fragile, and make setting up a development environment more difficult. There is a third option: installing Redis directly to the Docker image of our app. However, this would polute the image and make it less flexible.

Enter Fig, a tool to quickly build and start (isolated) development environments. Fig requires a single configuration file (default: fig.yml):

1

2

3

4

5

6

app:

build:.

links:

-redis

redis:

image:redis

The above file defines two containers: app (which contains our php application), and redis. The first container depends on the latter through the “links” section. Fig will make sure that, within the app container, “redis” resolves and points to that container.

Save the file, and run:

Shell

1

$fig-papp up

This should produce the following output:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

Creating app_redis_1...

Creating app_app_1...

Attaching toapp_redis_1,app_app_1

redis_1|[1]09Nov10:19:23.272# Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf

redis_1|_._

redis_1|_.-``__''-._

redis_1|_.-```.`_.''-._Redis2.8.17(00000000/0)64bit

redis_1|.-``.-```.```\/_.,_''-._

redis_1|(' , .-` | `, ) Running in stand alone mode

redis_1 | |`-._`-...-` __...-.``-._|'`_.-'| Port: 6379

redis_1 | | `-._ `._ / _.-'|PID:1

redis_1|`-._`-._`-./_.-' _.-'

redis_1||`-._`-._`-.__.-' _.-'_.-'|

redis_1 | | `-._`-._ _.-'_.-' | http://redis.io

redis_1 | `-._ `-._`-.__.-'_.-' _.-'

redis_1||`-._`-._`-.__.-' _.-'_.-'|

redis_1 | | `-._`-._ _.-'_.-' |

redis_1 | `-._ `-._`-.__.-'_.-' _.-'

redis_1|`-._`-.__.-' _.-'

redis_1|`-.__.-'

redis_1 | `-.__.-'

redis_1|

redis_1|[1]09Nov10:19:23.279# Server started, Redis version 2.8.17

redis_1|[1]09Nov10:19:23.279# WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

By default, Fig will start the containers in the foreground, blocking the terminal. To start in the background, add ‘-d’ to the command line.

Docker & Vagrant

As of version 1.6, Vagrant directly supports Docker and Docker containers through a new provider. Additionally, on platforms where Docker is not natively supported (Mac, Windows), Vagrant automatically starts a Linux-based (VirtualBox) vm which can then run a Docker container.

Let’s create a Vagrantfile for our app. We can re-use the existing Dockerfile, and let Vagrant use that:

1

2

3

4

5

6

Vagrant.configure("2")do|config|

config.vm.provider"docker"do|d|

d.build_dir="."

d.ports=["1337:1337"]

end

end

Save the file, and run ‘vagrant up':

1

2

3

4

5

6

7

8

9

10

11

12

13

14

==>default:Deleting the container...

==>default:Removing built image...

==>default:Building the container fromaDockerfile...

default:Image:d2717466cf97

==>default:Fixed port collision for22=>2222.Now on port2200.

==>default:Creating the container...

default:Name:docker_blog_default_1415529103

default:Image:d2717466cf97

default:Volume:/home/user/dev/docker_blog:/vagrant

default:Port:1337:1337

default:

default:Container created:539ba7edfee50fa0

==>default:Starting container...

==>default:Provisioners will notbe run since container doesn'tsupport SSH.

And once again, our app is available on port 1337, proxied to the container that Vagrant built.

Conclusion

If you’ve made it this far, you now know how to start a Docker container that runs your app, and link that container to other dependencies. Ofcourse this is but a little intro into the exciting world of Docker, which is rapidly changing and growing, and has much more to offer!

At my client Spotney, we have a pretty solid (and common) build infrastructure for our PHP projects; SVN commits are checked out by Jenkins, tests are run by PHPUnit, Sonar runs static code analysis, and artifacts are built and deployed to a staging environment by Phing. However, some of the code relies pretty heavily on (complex) db queries, adding the need for DbUnit style tests. The nature and quantity of the tests, combined with a slow VM (possibly related to this Xdebug issue) meant that our buildtimes were becoming prohibitively long.

An interesting post by @pascaldevink triggered a conversation and sparked an idea. I started working on our build setup, eventually resulting in a 60-70% decrease of our build times!

Here’s how I did it.

Starting point

Let’s assume we have a fairly standard Jenkins job. The job checks out an SVN repository, and periodically scans that repository for changes, triggering a new build if any are detected.

Each build of the job performs three steps:

Run phpunit

Run phing (target “build”)

Invoke Sonar (using the Jenkins Sonar plugin – this plugin also allows invoking Sonar as a post-build action, but that option requires Maven)

After the build steps, the job publishes the test and code coverage reports, and archives the generated artifacts.

Disabling code coverage and Sonar for regular builds

Two of the most obvious optimizations (also suggested by Pascal) are disabling code coverage on all tests and disabling Sonar runs during regular Jenkins builds. We define regular as either manually started by a user, or by an SCM trigger.

Disabling code coverage generation in PHPUnit is easy, simply remove the “coverage-xxx” elements from the logging section of your phpunit.xml configuration file (see this section of the PHPUnit manual). Disabling Sonar is trivial too, just remove the last build step from the job.

However, this is not an ideal solution: we do want to generate code coverage information and run Sonar at some point, such as during nightly builds, preferably without cloning our existing job. This means that we’ll need to skip code coverage and Sonar invocations on all but the scheduled (nightly) builds.

The Sonar plugin supports excluding SCM triggered builds (“Skip if triggered by SCM Changes”), but that only works if you use the post-build action. Additionally, we need to be able to change the PHPUnit configuration – one file to enable code coverage generation, one file to disable it.

Conditional build steps

The Conditional BuildStep plugin wraps one or more build steps in a conditional execution block. One of the possible conditions is the build cause, i.e. was the build triggered by a user, an upstream project, a timer, an SCM change, etc. etc.

First we define the steps that should be taken for each nightly build of our job. These steps should only be executed when the build is trigger by a timer.

We add a “Conditional steps (multiple)” build step, setting the condition to “Build Cause” and the Build Cause to “TimerTrigger”.

Then we add our three (original) build steps:

As stated before, regular builds are those that are triggered by a user or an SCM change.

We again add a “Conditional steps (multiple)” build step. The condition for this step is a little more interesting, as seen below. We combine two Build Cause conditions (one set to “UserCause”, the other to “SCMTrigger”) using the Or condition.

We then add two build steps: the first will run PHPUnit without code coverage (note that we are specifying a different configuration file here), the second one will run Phing.

Note that in the above build steps we’re invoking Phing from the shell instead of using the Phing plugin. Unfortunately this plugin is currently not supported as a conditional build step (probably because of this JIRA issue).

Build schedule

As a final step we need to update our build schedule.

This will ensure our job runs somewhere after midnight (between 12:00 AM and 2:59 AM to be precise).

The end result:

A nightly scheduled build, with all the bells and whistles enabled

User and SCM triggered builds run (a lot) faster

Please let me know if you think this post is useful, or if you have any other Jenkins/PHP optimization tips!

DocBook manual

A number of awesome pulls by johan162 updated our docbook manual (which has been in the works for some time) to a viable alternative. Next on the list are a few infrastructure issues and styling changes, after that the current manual will be ready to be replaced by our shiny new docbook version