Apache fun with Docker

Intro

Hi all, in this post I will show you an example of how you can use Docker to deliver a simple Apache http server for your website or application. I plan to make another posts that builds on top of this for a more advance use case.

So first a little introduction to what Docker is – I’ll be brief as there is plenty of information out there on it. Docker is a containerisation technology that allows you to wrap and enclose an entire service into a container for deployment on any Linux infrastructure without having to worry about the hosts configuration, setup, software dependencies, the lot. Docker Inc describes their product as

"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."

Prerequisites

To get started you will need to have Docker installed on your system, I am using CentOS, however any Fedora or Debian based system can be used. install instructions here.
You also need a good understanding of Apache.

Jump in

Feeling a little pro? Jump straight in via github.

Let’s Start

Ok, so we’re going to build an Apache webserver to serve up content, however that is really easy so I’ll make it a little bit more challenging by showing you how to set up vhosts, so that you can host more than one website from your Docker container.

First lets have a look at our Dockerfile;

FROM centos:latest
Maintainer vect0r
LABEL Vendor="CentOS"
RUN yum -y update && yum clean all
RUN yum -y install httpd && yum clean all
EXPOSE 80 81
#Simple startup script to aviod some issues observed with container restart
ADD run-httpd.sh /run-httpd.sh
RUN chmod -v +x /run-httpd.sh
#Copy config file across
COPY ./httpd.conf /etc/httpd/conf/httpd.conf
COPY ./example.com /var/www/example.com
COPY ./example2.com /var/www/example2.com
COPY ./sites-available /etc/httpd/sites-available
COPY ./sites-enabled /etc/httpd/sites-enabled

When Docker builds a container it builds it in layers. So when you go to create your Dockerfile you would see the essential stuff at the top and as you go down the file you will start to see more lower level configuration. Hence the first line. “FROM centos:latest” here we are telling Docker to go off and download the latest image of CentOS. This will be the base flavour our container uses.

“But I’m not using Centos.” I hear you say ^_^. It doesn't matter! As long as your using a Linux kernel, this will work. One of the beauties of Docker. Next we just add some fluffy meta data to our image. MAINTAINER AND LABEL. Maintainer would typically be your email address. In my case I've just added my tag. For the LABEL I've just specified CentOS as I'm using their base image. This interesting stuff starts happening at the RUN command. The RUN command shockingly tells Docker to run the subsequent command on the base image when building. Here we update all packages first then on the next line install Apache and clean yum.

Now at this point before we look at the rest let’s start looking at our Apache config, there will be subtle differences between Linux distros. I will follow CentOS’s style however I’m sure you can easily adapt this if your using Ubuntu. Right in our httpd.conf (apache.conf for Ubuntu) - we are mainly interested in setting our server to listen to ports 80 and 81, a port for our respective sites. You might want to also double check that sites-enable directory is also included. That’s it! The result of the defaults could be left as they are;

#make sure you have the below entries in your config #file.
Listen 80
Listen 81
IncludeOptional sites-enabled/*.conf

Inside our sites-enabled directory is where we’ll find our vhost.conf files for our two sites, in my case example.com.conf and example2.com.conf. You can specify this file however you want, example of mine looks like;

<VirtualHost *:80>
  ServerName www.example.com
  ServerAlias example.com
  DocumentRoot /var/www/example.com/public_html
  ScriptAlias /cgi-bin/ /var/www/cgi-bin/

<Directory "/var/cgi-bin">
  AllowOverride None
  Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
  Require all granted
</Directory>

  ScriptAlias /httpd/ /etc/httpd/

  ReWriteEngine on
  RewriteRule '/home/b/test'

<Location "/status">
  SetHandler server-status
  Order deny,allow
  Require all granted
  Allow from all
</Location>

  ErrorLog /var/www/example.com/error.log
  CustomLog /var/www/example.com/requests.log combined
</VirtualHost>

That’s all we need to worry about so now lets get back to our Dockerfile. So now that we've seen that Apache is listening to ports 80 and 81 we are now going to tell Docker to EXPOSE ports 80 and 81 on the docker container we build, as Docker doesn't do this automatically (that’s a good thing!).

EXPOSE 80 81

Next now that is done, you’ll see I’m using an ADD directive. This allows us to tell Docker to ADD a file from my desktop/host and put it into the container;

ADD <source-file-location> <container-file-location>

The source file goes into the container. So in my example run-httpd.sh is in the directory where I am running the build command and I’m placing the file on the root level of the container /. Important note about the ADD function is that it does not simply add local files. I can be used with URL supports certain compression types. See the Docker website for a more extensive explanation.

Let’s have a look at run-httpd.sh and why we need it;

#!/bin/bash

rm –rf /run/httpd/* /tmp/httpd*
exec /usr/sbin/apachectl –D FOREGROUND

So you might be wondering why we are removing files. This is simply to clean up the system in the event that you server crashed or was interrupted and didn’t shutdown properly. If that does happen and these directories aren’t cleaned out Apache will fail to start as the system will think it’s already running.

You can see here we are starting up Apache with its cli command as oppose to using the more conventional service or systemctl. This is more of a best practice convention if anything. There is nothing stopping you from using these utilities, however with all the different base images out there some may not have these services available by default!
Another point to highlight here is that we need to start Apache in the FOREGROUND, this may sound unconventional as Apache is usually started as a background daemon. This is done because of the way Docker works. When you run a Docker container, the containers only remain up for the lifetime of the command you give it. This is determined by the stdout output. So if we started Apache in the usual manner, as a daemon, then our container would start then immediately stop as it would think its purpose is complete.

Our next command we give Docker is RUN, here we are telling Docker to run a command at build time. In our example we are simply making the script we just added executable;

RUN chmod -v +x /run-httpd.sh

On to the COPY function lines. COPY is similar to the ADD function in that it allows use to place files in our container. However COPY isn’t as sophisticated as ADD, copy simply puts local files into your container. Why not use ADD? Well COPY is just more transparent in its functionality so it’s usually preferred when copying config files.

COPY ./httpd.conf /etc/httpd/conf/httpd.conf
COPY ./example.com /var/www/example.com
COPY ./example2.com /var/www/example2.com
COPY ./sites-available /etc/httpd/sites-available
COPY ./sites-enabled /etc/httpd/sites-enabled

I have taken the apache files we look at earlier and placed them in the relevant places.
Last but not least. CMD this is going to execute our run-httpd.sh script once all the above is executed. You have just completed a Dockerfile – was easy huh!

In action

Let’s see this in container in action. First we need to build this sucker, make sure your in run the below command in the directory your Dockerfile is located;

docker build –rm –t <username>/httpd-proxy .

You can call your image whatever you want if you don’t like httpd-proxy. When you run this you will see a bunch of stuff output to your console, this is Docker building your container in the layered instructions we gave it. Once that’s finished if you type;

docker images

You will see the image you’ve just created listed. Once this is built you can use this image to create your container. Now to create the container we also need to bind our host ports to our container, otherwise our web request won’t go through! We can do this as part of our run command like so;

docker run –d –p 80-81:80-81 <username>/httpd-proxy

Here we are telling docker to create a container the –d tell its to daemonise the container and the –p is binding the ports. We are saying bind our host ports 80-81 to our containers ports 80-81. Then we are simply giving the name of the image we want to use. When our container is build it will be given a random name like “firey-scotty”, if we want to specify a name yourself we can use the --name option. In my example I’m happy with a random name. Once this command is executed you can see the container running via;

docker ps

Now you can open your browser and navigate to your sites via http://localhost and http://localhost:81

Conclusion

To conclude, we have gone through setting up, building, and then running our Docker container. I hope this demonstrates and makes you think about the fun that could be had with Docker.
Sorry if this went on for too long. Please leave a comment if this helped you, if you have any questions, or if you have any recommendations. I look forward to hearing from you.