Taming a Legacy Application with Docker

By Gavrie Philipson | 2016-09-28

I’ll be the first one to admit that I have been spoiled by programmable infrastructure. It becomes easy to forget that not too long ago we used to spend hours upon hours on installing a physical server or VM, when nowadays all it takes is a simple docker run to do so many things.

That’s why I sighed inwardly when I needed to install a staging server for a legacy LAMP application.

Staging TestRail

One of my clients uses TestRail, a Web based test management tool. They have it installed in a VM on their premises. They asked me to do some work related to automated reporting of test results via the TestRail API.

The first thing I usually do in such cases is install a staging server: A duplicate of the production server on my laptop (or in the cloud somewhere) that allows me to play with it without endangering the production server. To get such a staging server in this case would mean installing TestRail from scratch, and then feeding it with a copy of the data from the customer’s production database. While it may have been possible to clone the production VM and use that instead, this would be both heavyweight (a big fat VM) and nonreproducible (what went into this VM?).

So, on to the installation. Unfortunately TestRail do not provide a Docker image or a VM image. All they have is instructions on installing a Linux or Windows server with the relevant dependencies, adding a proprietary PHP extension, then installing a LAMP environment, creating their database, and some more stuff.

It might not be so bad to do this once, but it never ends there. You always end up needing to reinstall several times. It also would be nice to be able to share the result with other developers so they can easily create their own staging servers.

Dockerizing TestRail

What would be really nice is to have a Dockerfile that would do all the above work, making it simple to install and start a new staging server in a container. I looked around a bit to see if someone had already done this. I did find some prior attempts (such as this one), but these only created a base image with some dependencies and not a full solution.

I then proceeded to create a Dockerfile that does a full installation.

There were several challenges involved:

  • TestRail requires a database such as MySQL and a Web server such as Apache. To create a standalone solution, all of these should be included in the image.
  • Several changes need to be made to system configuration files.
  • A new database must be created during the installation.
  • To complete the installation, an installation wizard must be run via the Web UI. This means that there is plenty of state to keep around.

If I could automate all this, it would be possible to create a new staging server with one docker run command.

Creating the Base Image

The TestRail installation requirements recommend installing Ubuntu LTS, as well as MySQL, PHP and Apache. In short, a standard LAMP stack. We pick Ubuntu 14.04 LTS, a.k.a. trusty, since the latest release includes PHP 7 which doesn’t seem to be supported by TestRail.

We therefore write the following Dockerfile:

FROM ubuntu:trusty

RUN apt-get update && apt-get install -y --no-install-recommends \
    php5 php5-cli php5-mysql php5-curl \
    mysql-server \
    curl \
    unzip \
    && rm -rf /var/lib/apt/lists/*

We add curl since we’ll need it later to download things, and unzip since TestRail comes as a zip file.

Installing ionCube

The ionCube PHP extension that TestRail requires is proprietary software, so we cannot distribute it. Instead, we download it automatically in the Dockerfile. We then follow the instructions to install it:

RUN curl -O http://downloads3.ioncube.com/loader_downloads/ioncube_loaders_lin_x86-64_5.1.2.tar.gz && \
    tar vxfz ioncube_loaders_lin_*.tar.gz && \
    rm -f ioncube_loaders_lin_*.tar.gz

RUN echo "zend_extension=/ioncube/ioncube_loader_lin_5.5.so" > /etc/php5/cli/php.ini.new && \
    cat /etc/php5/cli/php.ini >> /etc/php5/cli/php.ini.new && \
    mv /etc/php5/cli/php.ini.new /etc/php5/cli/php.ini && \
    echo "zend_extension=/ioncube/ioncube_loader_lin_5.5.so" > /etc/php5/apache2/php.ini.new && \
    cat /etc/php5/apache2/php.ini >> /etc/php5/apache2/php.ini.new && \
    mv /etc/php5/apache2/php.ini.new /etc/php5/apache2/php.ini

Note that we configure ionCube for both the CLI and Apache PHP versions, since both are used by TestRail: It uses the CLI version to run scheduled tasks.

Installing TestRail

Since TestRail is proprietary software, we cannot redistribute it, nor can we download it automatically since the download requires a username and password. We therefore assume that you have downloaded it already to the current directory.

We proceed to copy TestRail to the image, and unzip it:

COPY testrail-*.zip /
RUN cd /var/www/html && unzip -q /testrail-*.zip

Completing the Installation Automatically

According to the instructions, to complete the installation, we now need to:

  • Create the TestRail database
  • Run its Installation Wizard
  • Configure a background task to run

The Web-based Installation wizard asks several questions, and as a result it creates a config.php file. It would be great to avoid running it, and instead provide the answers directly. To achieve this, I ran the wizard once, and then saved the config.php file. I then dumped the database contents using mysqldump testrail > testrail.sql, so we can skip the wizard entirely. Instead, we copy the config.php file to its location and recreate the database from the dumped data.

Note that if you have an existing database that you want to use, you can dump is as described and replace the testrail.sql file with your version.

To complete the process, we copy the mentioned files to the image. We also provide a script, run.sh, that will run when the container starts to complete the process.

COPY config.php /var/www/html/testrail/config.php
COPY testrail.sql /
COPY run.sh /

CMD /run.sh

Preparing the Container Runtime

We will now describe the run.sh script that runs when the container starts.

The first part creates the log directory required by TestRail, and starts the necessary background task via cron.

mkdir /var/www/html/testrail/logs
chown www-data /var/www/html/testrail/logs

echo '* * * * * www-data /usr/bin/php /var/www/html/testrail/task.php' > /etc/cron.d/testrail

The next parts run MySQL, and creates the database. Note that init is not running in the container, but we can still start MySQL as usual by running its init script.

/etc/init.d/mysql start

echo "CREATE DATABASE testrail DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;" | mysql -u root
echo "CREATE USER 'testrail'@'localhost' IDENTIFIED BY 'newpassword';" | mysql -u root
echo "GRANT ALL ON testrail.* TO 'testrail'@'localhost';" | mysql -u root

We now load the contents of the database, originally created by the Installation Wizard:

mysql testrail < testrail.sql

Finally, we start Apache and go to sleep so that the container will stay active.

/etc/init.d/apache2 start
sleep infinity

Building the Image and Starting the Container

We can now build the Docker image:

docker build -t testrail .

Finally, we can start the container:

docker run --name testrail -d -p 7070:80 testrail

Note that we run the container in detached mode (-d), and forward its port 80 to our local port 7070.

We can now log into TestRail by browsing to:

The default user is admin@admin.com, and the password is admin.

Keeping Your Data

Remember that any data you create will be lost when stopping the container. If you want to keep your data, connect to the container using docker exec, then dump the data with mysqldump and docker cp it to your computer as testrail.sql. The next time you can use it to restore the database.

Summary

We described how we created a fully automated process for installing and configuring a legacy LAMP application. Such a process makes it easy to create and destroy staging servers as needed without relying on any pre-existing state.

If you want to recreate the Docker image, or create your own variant, feel free to get the above code from its GitHub repository.


Banner image by ballance

comments powered by Disqus