LAMP service with Docker

Docker Round 2…..Fight!

Back at ya with some more Docker fun. In this post I’ll build upon the Apache fun with Docker post. In the last post we saw an example of using Docker to set up a webserver. This post will show you how you can use Docker to produce a complete LAMP stack, which you could then use as a template to deliver a lot of different LAMP sites off this one Docker image. You sure your ready bub?

Prerequisites

What do you need to follow this post? Firstly you’ll need to install Docker, install instructions here. You’ll also need:

  • A good understanding of Apache HTTP Server
  • A good understanding of PHP
  • A understanding of Supervisord
  • A good understanding of MySQL
  • A decent understanding of Docker, if you’ve completed my previous post then that would be adequate.
Jump in

As always, if you’re feeling pro, jump on ahead. You can use my pre-built Docker image or follow along with the post and build it yourself here.

Suit up

Once you’ve downloaded the files off Github, let’s quickly skim through the Dockerfile and look at what is going on.


    FROM ubuntu:trusty
    MAINTAINER vect0r

    # Install packages
    ENV DEBIAN_FRONTEND noninteractive
    RUN apt-get update && \
      apt-get -y install supervisor git apache2 libapache2-mod-php5 mysql-server php5-mysql pwgen php-apc php5-mcrypt && \  
      echo "ServerName localhost" >> /etc/apache2/apache2.conf

    # Add image configuration and scripts best practice to start service from start up script to avoid problems observed with starting via service directive
    ADD start-apache2.sh /start-apache2.sh
    ADD start-mysqld.sh /start-mysqld.sh
    ADD run.sh /run.sh
    RUN chmod 755 /*.sh          # */ delete this comment
    ADD my.cnf /etc/mysql/conf.d/my.cnf
    ADD supervisord-apache2.conf /etc/supervisor/conf.d/supervisord-apache2.conf
    ADD supervisord-mysqld.conf /etc/supervisor/conf.d/supervisord-mysqld.conf

    # Remove pre-installed database
    RUN rm -rf /var/lib/mysql/*   # */ delete this comment

    # Add MySQL utils
    ADD create_mysql_admin_user.sh /create_mysql_admin_user.sh
    RUN chmod 755 /*.sh    # */ delete this comment

    # config to enable .htaccess
    ADD apache_default /etc/apache2/sites-available/000-default.conf
    RUN a2enmod rewrite

    # Configure /app folder with sample app
    RUN git clone https://github.com/V3ckt0r/hello-docker.git /app
    RUN mkdir -p /app && rm -fr /var/www/html && ln -s /app /var/www/html

    #Enviornment variables to configure php
    ENV PHP_UPLOAD_MAX_FILESIZE 10M
    ENV PHP_POST_MAX_SIZE 10M

    # Add volumes for MySQL 
    VOLUME  ["/etc/mysql", "/var/lib/mysql" ]

    EXPOSE 80 3306
    CMD ["/run.sh"]

I will only go through the noteworthy points here as I expect you to be able to understand the majority of what is going on here without explanations. So let’s start with that odd ENV param on line 4. This is specific to the Ubuntu base image, it’ll be a bit long winded and slightly off topic to talk about this in great detail. For now just accept that this needs to be there so avoid seeing some fairly ominous error messages later on! You can do some background reading on this here.
Next we install all our LAMP stack dependencies and then move onto the configuration. What we are doing here is, as before, using start up scripts to start our services. However we are using Supervisord to actually execute these start up scripts. Supervisord benefits us in this context as it watches our services and automatically starts them up for us on boot, or in the event that the application crashes.
Our Dockerfile then clears out any default database files that get installed then configures the Mysql admin user. Let’s take a quick look at it:

#!/bin/bash

/usr/bin/mysqld_safe > /dev/null 2>&1 &

RET=1
while [[ RET -ne 0 ]]; do
    echo "=> Waiting for confirmation of MySQL service startup"
    sleep 5
    mysql -uroot -e "status" > /dev/null 2>&1
    RET=$?
done

PASS=${MYSQL_PASS:-$(pwgen -s 12 1)}
_word=$( [ ${MYSQL_PASS} ] && echo "preset" || echo "random" )
echo "=> Creating MySQL admin user with ${_word} password"

mysql -uroot -e "CREATE USER 'admin'@'%' IDENTIFIED BY '$PASS'"
mysql -uroot -e "GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%' WITH GRANT OPTION"


echo "=> Done!"

echo "========================================================================"
echo "You can now connect to this MySQL Server using:"
echo ""
echo "    mysql -uadmin -p$PASS -h<host> -P<port>"
echo ""
echo "Please remember to change the above password as soon as possible!"
echo "MySQL user 'root' has no password but only allows local connections"
echo "========================================================================"

mysqladmin -uroot shutdown

In order for us to create a set up the root account we need to bring up myqld, hence we begin with mysqld_safe.

Next we have a simple loop that checks for a successful startup of MySQL, while RET is not equals to 0 then echo a message sleep for 5 seconds and check the status of MySQL. We keep doing this loop until we get a success code back from $? ($? Returns the status code of the last command, so if MySql status is successful a code of 0 is returned). It is worth a mention that you would of noticed we are routing all output from this command to /dev/null, this is because it will look very confusing on your terminal when bundled together with your Docker logs.

Then we create the variable PASS for our randomly generated password and use this variable in MySql when we create the admin user. Once the grants are given we can echo out the information so it gets captured in the Docker logs. Lastly we shutdown MySql, don’t forget that we want Supervisord to handle the MySql process.
Back to the DockerFile, we get to the point of configuring Apache. If you followed the last post this part will look familiar to you. Our Apache vhost configuration is in the apache_default file. This is all standard stuff. With the below line we add this into the 000-default.conf. Please note – the 000-default file is specific to Apache installations on Ubuntu, you won’t find this on Fedora based systems.
ADD apache_default /etc/apache2/sites-available/000-default.conf
RUN a2enmod rewrite

The a2emod rewrite link can be skipped if you are not using rewriting in Apache, however might as well include it as this is a quiet common requirement.
Once our Apache config is set up we need supply our content. There are multiple ways you can include your content, the method I prefer is using a git repo to pull the content into your container, remember when we installed git back at the top of the DockerFile.

RUN git clone https://github.com/V3ckt0r/hello-docker.git /app
RUN mkdir -p /app && rm -fr /var/www/html && ln -s /app /var/www/html

Here we’re telling Docker to clone a repo from Github then place it in the directory /app. On the next line we remove all the default html content that comes with apache and we create a symlink to our content location /app. On this line I included the mkdir –p /app option, in case you don’t want to use a git repo. If you don’t want to use it you can remote to git clone line and put a ADD command after the second line to add local content.
System environments can be setup with the ENV tag as demonstrated below;

ENV PHP_UPLOAD_MAX_FILESIZE 10M
ENV PHP_POST_MAX_SIZE 10M

Docker volumes is a new concept we haven’t see yet. The VOLUME tag allows us to tell Docker about a resistant path we want to use for our application. When Docker builds a container from an image it builds it out of a series of read only layers, you see these layers when creating and pulling images. Once the container is fully initialised, then Docker adds a read/write layer on top of it all and that becomes your active container. The combination of layers is referred to as a Union File System.
When you want to have a location where you are able to share resources between different containers or have files accessible outside of the container, this is where Volume’s come in handy. Volumes essentially allow you to map host filesystem paths into your containers. So the line

VOLUME ["/etc/mysql", "/var/lib/mysql" ]

Is mapping out host location /etc/mysql on our container location /var/lib/mysql. In my build we aren’t using this VOLUME but I have included it to provide give you an example
Lastly we EXPOSE both ports 80 and 3306 for our Apache and MySql instances. Then start the run.sh, that does some validation checks on the database before executing Supervisord.
Now that we’ve gone through what the Docker file is actually doing. We are ready to run our container.
If you have built your Dockerfile yourself then make sure you build your image:

Docker build –t <username/name>

And run your container with:

Docker run –d –p 80:80 –p 3306:3306 <username/name>

Then just open up a web browser and check it out http://localhost/

Conclusion

We went through a lot in the post. I hope this has demonstrated a possible use case for Docker. In the article we have essentially containerised our web application for deployment. If you are in an environment where you are managing multiple different web applications, this method of bundling all your application dependencies into one container will make managing your Docker host much easier. Not to say that this is the way to go every time, this is just one road of many. Go find yours ;-)