How to setup a LEMP server inside a Docker.IO Container

updated August 7th, 2013 incorporating fixes by Mr. Chizzlebear.

Docker is awesome. There are great explanations of what it is and how it works in this awesome collection of quotes.

In this tutorial, we will setup a self-contained LEMP (GNU/Linux, Nginx, MariaDB, PHP-FPM) stack inside a minimalist Ubuntu 12.04 Docker.io container.

Note: I’m new to Docker too, so if you have any tips or links to other tutorials, leave them in a comment!

If you do not already have docker.io installed, go ahead and set it up. It’s cool, I’ll give you 5 minutes..

Ready? Ok let’s get this show on the road!

The Container

Start a minimal Ubuntu 12.04 (aka “Precise”, an LTS release) image that docker automatically fetches for you:

aleckz@prometheus:~$ docker run -i -t ubuntu:12.04 /bin/bash
root@b3f5e7de5ad4:/# uname -a
Linux b3f5e7de5ad43.8.0-26-generic #38-Ubuntu SMP Mon Jun 17 21:43:33 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux
root@b3f5e7de5ad4:/#

The Repositories

Great, now we are running a shell inside a container! It has it’s own filesystem, process space, and apps. The minimalist image is pretty bare, so..

Setup repositories with latest nginx (Engine-X, the ‘E’ in LEMP), PHP5 (using FPM), and MariaDB (the trending MySQL fork these days) using signed PPAs. Note: I’m using Ondrej’s builds to get the latest PHP5 support (thanks Ondrej!)

root@b3f5e7de5ad4:/# echo "deb http://ppa.launchpad.net/nginx/stable/ubuntu precise main" >> /etc/apt/sources.list
root@b3f5e7de5ad4:/# echo "deb http://ppa.launchpad.net/ondrej/php5/ubuntu precise main" >> /etc/apt/sources.list
root@b3f5e7de5ad4:/# echo "deb http://archive.ubuntu.com/ubuntu/ precise universe" >> /etc/apt/sources.list
root@b3f5e7de5ad4:/# echo "deb http://ftp.osuosl.org/pub/mariadb/repo/10.0/ubuntu precise main" >> /etc/apt/sources.list
root@b3f5e7de5ad4:/# apt-key adv --keyserver keyserver.ubuntu.com --recv-keys C300EE8C E5267A6C 0xcbcb082a1bb943db
root@b3f5e7de5ad4:/# apt-get update

A small patch

The stock image is really bare, so lets install a few utils, and make a tiny fix.

* whiptail – for displaying dialogs in the CLI via perl
* mlocate – to make locate/updatedb work (to quickly find files)
* nano – for simple file edits
* /etc/mtab – a mounted file systems table to make MySQL happy

root@b3f5e7de5ad4:/# cat /proc/mounts > /etc/mtab
root@b3f5e7de5ad4:/# apt-get install whiptail nano mlocate

The EMP

root@b3f5e7de5ad4:/# apt-get install nginx php5-fpm mariadb-server php5-mysqlnd

All together this will add ~170MB to your GNU/Linux container.

Before we do anything else, let’s save the current state of the container.. while it’s running!

Open up another terminal, find the ID of the container, and commit it to a new image!

aleckz@prometheus:~$ docker ps
ID                  IMAGE                            COMMAND             CREATED             STATUS              PORTS
b3f5e7de5ad4        ubuntu:12.04        /bin/bash           20 minutes ago       Up 11 hours ago
aleckz@prometheus:~$ docker commit -m "initial LEMP package install" b3f5e7de5ad4 PreciseLempFreshInstall
aleckz@prometheus:~$ docker images
REPOSITORY                TAG                 ID                  CREATED              SIZE
ubuntu                    12.04               8dbd9e392a96        3 months ago         131.5 MB (virtual 131.5 MB)
ubuntu                    12.10               b750fe79269d        3 months ago         24.65 kB (virtual 180.1 MB)
ubuntu                    latest              8dbd9e392a96        3 months ago         131.5 MB (virtual 131.5 MB)
ubuntu                    precise             8dbd9e392a96        3 months ago         131.5 MB (virtual 131.5 MB)
ubuntu                    quantal             b750fe79269d        3 months ago         24.65 kB (virtual 180.1 MB)
PreciseLempFreshInstall   latest              b6b4d496e1bc        About a minute ago   400.7 MB (virtual 532.2 MB)

Well look at that, you just created a docker.io container image! This image can be shared with others, and can be extended safely knowing you can always fallback to its original state.

We could continue working in this container, but let’s try something else: shut down the container by exiting its shell.

root@b3f5e7de5ad4:/# exit

aleckz@prometheus:~$ docker ps
ID                  IMAGE               COMMAND             CREATED             STATUS              PORTS
aleckz@prometheus:~$

No containers are running. Yep, it down, including all the processes inside.
We could restart the container and continue where we left off. But instead, lets spawn a new shell (inside a new container) using the new snapshot you just created and finish configuring that way!

aleckz@prometheus:~$ docker run -i -t PreciseLempFreshInstall /bin/bash
root@c98da5beba43:/#

Notice how the newly spawned container has a new hostname (representing a new ID).

In git/svn terms, this is like cloning a repository and then working on it, eventually committing your changeset!

Stacking the database

MySQL needs a little loving before it will work inside our container. The default config’s flush-method for InnoDB tables doesn’t like O_DIRECT access to tmpfs, so let’s change it:

root@c98da5beba43:/# apt-get install nano
root@c98da5beba43:/# nano /etc/mysql/my.cnf
now find and change this line:
innodb_flush_method    = O_DIRECT
to look like this:
#innodb_flush_method    = O_DIRECT

After commenting out that line, MySQL should start cleanly.

Now, create a new user and a database catalog for testing.

root@c98da5beba43:/# service mysql start
root@895d41d8a324:/# mysql -uroot -p
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 36
Server version: 10.0.3-MariaDB-1~precise-log mariadb.org binary distribution

Copyright (c) 2000, 2013, Oracle, Monty Program Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> create user 'tester'@'localhost' identified by 'captain-h4mm3r';
MariaDB [(none)]> create database testing;
MariaDB [(none)]> grant all on testing.* to 'tester'@'localhost';
MariaDB [(none)]> use testing;
MariaDB [testing]> create table messages (messageId int unsigned not null auto_increment primary key, message text, author varchar(32));
MariaDB [testing]> insert into messages set message = "I wish i had some more quarters.", author = "Dr. Horrible";
MariaDB [testing]> insert into messages set message = "You won't be needing quarters once I'm done with you!", author = "Captain Hammer";
MariaDB [testing]> exit
Bye
root@895d41d8a324:/#

Great, we have a live running database with some data in it. Let’s get PHP-FPM running and create a site to host using Nginx.

Stacking Nginx

Create the site’s location:

root@c98da5beba43:/# mkdir -p /var/www/testing
root@c98da5beba43:/# chown -R www-data:www-data /var/www

Make the landing page:

root@c98da5beba43:/# nano /var/www/testing/index.php

With some php that accesses the database:

<?php
$dbh = new PDO('mysql:host=localhost;port=3306;dbname=testing', 'tester', 'captain-h4mm3r');
$stmt = $dbh->prepare("select * from messages");
$stmt->execute();

$messages = [];

while($row = $stmt->fetch(PDO::FETCH_OBJ))
	$messages []= $row;

echo "<pre>";
var_dump($messages);
echo "</pre>";

?>

Then replace nginx’s default config:

root@c98da5beba43:/var/www# nano /etc/nginx/sites-available/default

with this:

server {
	listen 80 default_server;
	listen [::]:80 default_server ipv6only=on;

	root /var/www/testing;
	index index.php

	# Make site accessible from http://localhost/
	server_name localhost;

	location / {
		try_files $uri $uri/ /index.html;
	}

	location ~ \.php$ {
		fastcgi_split_path_info ^(.+\.php)(/.+)$;
		# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini

		fastcgi_pass unix:/var/run/php5-fpm.sock;
		fastcgi_index index.php;
		include fastcgi_params;
	}

}

And start your engine!

root@c98da5beba43:/# service nginx start

Stacking PHP-FPM

This is the one component that will work straight out of the box:

root@c98da5beba43:/# service php5-fpm start

Done

Bam! Our self-contained web-server should be up and running. Let’s test it using the lynx command-line browser: (Ah the good old days!)

root@c98da5beba43:/# apt-get install lynx
root@c98da5beba43:/# lynx http://localhost

You should see something like this:
lynx lemp test

16 thoughts on “How to setup a LEMP server inside a Docker.IO Container

  1. Thanks for the post. I found 2 typos in here:

    root@b3f5e7de5ad4:/# echo “deb deb http://ftp.osuosl.org/pub/mariadb/repo/10.0/ubuntu precise main” >> /etc/apt/sources.list
    There you have ‘deb’ twice, resulting in an apt-get error

    And in the PHP file, you’re missing the last semicolumn

  2. Great catches! Thanks Mr. Chizzlebear!

  3. But how do you get the data in mysql to persist? It’s in tmpfs so every time your container goes down you lose the database. What is the ideal way to handle that? nfs mounts to a nas? Thanks!

  4. Pingback: Docker | MÓDOLO

  5. How did you get upstart to work?

    Following your instructions produces these errors:

    initctl: Unable to connect to Upstart: Failed to connect to socket /com/ubuntu/upstart: Connection refused

    I’m running docker 0.6.5 so not sure if there were major changes between then.

    Matt

  6. I’ve followed your tutorial but wasn’t able to reach the nginx inside the container from the outside, how do you do it?

  7. Pingback: What I’m Reading December 12 | inthecloud247

  8. For those considering Docker, here are three arguments for Docker that are clear benefits and cannot be argued against:

    A) Ability to easily diff containers. It improves debugging, allows faster sharing of the environment, and quicker deployments. This is a big plus and there aren’t any competing solutions.

    B) Ability to debug DevOps (like chef recipes) locally without having to pay the penalty of the slow boot of a VM or AWS instance. http://flux7.com/blogs/docker/docker-tutorial-series-part-1-an-introduction/

  9. One of our developers is using Docker.io. I am very interested in using docker to set up a nginx fastcgi server with modsecurity and SPDY enabled and a few other optional plugins enabled. I want to run at least version 1.4.7 of nginx. I take it I will have to use wget to get source code into the container’s
    /usr/src and then build from there? Any gotchas to look out for?

  10. Pingback: Docker gebruiken om een testomgeving te creëren - Michael Boumann

  11. Considering this post is a year old and the Docker codebase and community have grown quite a bit, you probably already realize or have discovered this bit I’m about to share. It would be considered bad practice to stack all of these programs into one container because Docker containers operate with a single process. The L in LEMP can be considered your host machine, however, nginx, mysql (mariadb) and PHP should all be managed within their own containers. Its also very possible to host docker within docker so that you could create a container of containers, so-to-speak.

  12. Pingback: Docker gebruiken om een testomgeving te creëren - Michael Boumann Tech

Leave a Reply

Your email address will not be published. Required fields are marked *