Posted by Thiago Marini on Oct 16, 2015 Creating a virtual host environment with Docker

I was playing with Docker last week and decided to replicate a virtual host environment where different domains should point to different containers running in the host machine. The same way we can easily do with Apache or Nginx.

After struggling a bit to grasp Docker’s concepts and gotchas, I was able to do it and found the result to be pretty cool. Actually if I ever need to create such environment I’ll use Docker.

So this post is about my experience playing with Docker, I hope it can be useful to someone who needs to create a virtual host environment using Docker.

The scenario

Imagine you have a bunch of apps in a variety of programming languages, each application should respond to a different domain and you want to bundle everything in the same machine.

To solve this problem I’ll be using a micro EC2 instance running Linux AMI as a sandbox.

Learning Docker

I’m not going to dive into Docker concepts on this post, as there are tons of excellent posts out there on this subject. If you don’t know about Docker, check out these tutorials that helped me learn it:

So I’m assuming you have done your homework and understand Docker’s basics at this point. Let’s get busy!

Installing Docker on EC2 Linux AMI

sudo yum install - y docker

Add the ec2-user to the Docker group so you can execute Docker commands without using sudo (optional):

sudo usermod - a - G docker ec2 - user

Exit the EC2 and login again to get permissions.

Start Docker:

sudo service docker start

Configure EC2 to start the Docker daemon at boot:

sudo chkconfig docker on

Creating images to build the containers

You can build your own images from scratch or search and pull images from Dockerhub. I’ll show how to do it both ways, no worries.

First app: Apache + PHP

Imagine that one of the applications was written in PHP, my favorite language. Let’s build the image from scratch for this one.

Create a folder called php-apache to host the app:

mkdir php - apache

Enter it:

cd php - apache

Place a DockerFile with the following content inside the folder:

vim Dockerfile

FROM ubuntu : 12.04 # Install dependencies RUN apt - get update - y RUN apt - get install - y git curl apache2 php5 libapache2 - mod - php5 php5 - mcrypt php5 - mysql # Install app RUN rm - rf / var / www /* ADD src / var / www # Configure apache RUN a2enmod rewrite RUN chown - R www - data : www - data / var / www ENV APACHE_RUN_USER www - data ENV APACHE_RUN_GROUP www - data ENV APACHE_LOG_DIR / var / log / apache2 EXPOSE 80 CMD [ "/usr/sbin/apache2" , "-D" , "FOREGROUND" ]

The FROM instruction sets the base image for subsequent instructions, in this case I’ve chosen Ubuntu.

RUN instructions will run those commands in new layers on top of the base image.

The ADD instruction is really important, it will get all content on the src folder and copy it to the apache root, in this case /var/www . So we need to create the src folder:

mkdir src

And place our demo PHP application in it:

echo "<?php phpinfo();" > src / index . php

And finally the ENV instruction sets environment variables.

Now we’re good to go, let’s build our image from the Dockerfile :

docker build - t thiagomarini / apache - php .

Note that I’m using the Dockerhub naming standard to build my image.

You can check if the image was created by running this command:

docker images

But if you list containers, you’ll see none:

docker ps

Let’s run a container from this image:

docker run - p 80 : 80 thiagomarini / apache - php

If you run docker ps you will see the container, and if you access your EC2 from it’s public IP you’ll see the phpinfo() page.

Stop the container with `CTRL+C` as it's running on the foreground.

Second app: Tomcat + Java

This time we’ll not build the image, we’ll pull one from Dockerhub.

docker pull tomcat

Run it binding port 8888 of the container to port 80 of the EC2.

docker run - it -- rm - p 8888 : 80 tomcat : 8.0

Access it from the EC2 public IP on port 8888 and you should see a Tomcat welcome page.

Remember to open all ports we work with on your EC2 security group.

Now we have 2 images and we can run them, good!

Running both containers at the same time

To run both containers at the same time we need to run them in the background, notice the -d param on the commands.

Give port 80 to the Apache PHP app:

docker run - p 80 : 80 - d thiagomarini / apache - php

And give port 90 to Tomcat:

docker run - it - p 8888 : 90 - d tomcat : 8.0

Now access you EC2 through its public IP and you should see the PHP app on port 80 and Tomcat on port 8888.

We are advancing, but our problem has not been solved yet, we still need to point different domains to those containers and access all of them on port 80.

Because the containers are not running on the foreground anymore we can’t stop them with CTRL+C , we need to list the containers:

docker ps

And use the container ID to stop them:

docker stop { container_ID }

Now comes the moment of truth, we’ll use a Nginx container as a reverse proxy to distribute port 80 requests to each of these containers depending on the domain that originated the request.

The solution: docker-gen + Nginx reverse proxy

So far we are binding our EC2 ports to container ports, but we need to bind domains to containers. For that we’ll use docker-gen which generates reverse proxy configs for Nginx, it also reloads Nginx when containers are started/stopped.

We’ll use the jwilder/nginx-proxy project, which is already configured and good to go:

docker pull jwilder / nginx - proxy

Run this image on port 80:

docker run - d - p 80 : 80 - v / var / run / docker . sock :/ tmp / docker . sock jwilder / nginx - proxy

Now if you access your EC2 from its public IP you’ll get a 503 Service Temporarily Unavailable response, which is good! Nginx is just saying it doesn’t know how to serve your request as it is not configured to point to anywhere.

Given that your domains are pointing to the EC2, we can run our containers using the VIRTUAL_HOST param, which will do all the magic and bind the domains to the running containers.

docker run - d - e VIRTUAL_HOST = domain1 . com thiagomarini / apache - php

docker run - d - e VIRTUAL_HOST = domain2 . com tomcat

That’s it :)

Hopefully if you access domain1.com and domain2.com you’ll see the PHP and Java apps respectively.

Automated deployment

The virtual host environment is now working as expected but if you need to do any changes you’ll have to do it manually by updating your images and running containers from them.

If you need automated deployment there are some solutions that could automatically do deploys using Github, Dockerhub and web hooks.

Have a look at these tools for example: