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
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
Great catches! Thanks Mr. Chizzlebear!
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!
You don’t have to go the NFS route Franky! Docker lets you persist data by mounting an external folder into the container when you first create it. Think of it as a permanent symlink from the container out to the host. Any changes made to files in the folder (ie: MySQL’s data folder) will appear on the host. Check out http://blog.docker.io/2013/07/docker-0-5-0-external-volumes-advanced-networking-self-hosted-registry/
Also have a look at the ‘data only container’ pattern, which makes things environment agnostic. Because using an external folder is environment specific.
Pingback: Docker | MÓDOLO
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
Try this fix: https://github.com/dotcloud/docker/issues/1024
I’ve followed your tutorial but wasn’t able to reach the nginx inside the container from the outside, how do you do it?
To break the container boundary with the host, you need to take your awesome docker image and run it with a few extra parameters, eg: “docker run -i -t -p 80:80 yourdockerimage /bin/bash” this will spawn a fresh container with a www port mapping so that you can connect to nginx from outside.
More details here: http://docs.docker.io/en/latest/use/port_redirection/
Pingback: What I’m Reading December 12 | inthecloud247
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/
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?
Pingback: Docker gebruiken om een testomgeving te creëren - Michael Boumann
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.
Pingback: Docker gebruiken om een testomgeving te creëren - Michael Boumann Tech