Docker_Container_Engine_Logo_Grouped_White_Background_Logo_Only.pngWhenever you start mentioning that one should deploy a Web application using Docker, you start seeing your fellow PHP developer's eyes roll over due to the intrinsic difficulties with setting up these types of deployment pipes. Most of the time, the issues are related to questions like: "how will we get this file into the container depending on the environment we are deploying to?", and "why do we have to set up all these tools, only to get a Web application from the testing environment to the production environment?". Indeed, these questions, and many more, can cause many headaches to both the developers and the devops/sysadmin operators.

 

This is where file-based portable environments, or FBPEs, step in. An FBPE is simply a technique, by which a programmer makes sure that a deployment script, whether it be a Dockerfile, a set of Docker Compose files, or any other deployment configuration file, does not contain any concrete references to files, or resources, that are outside of the environment that is to be deployed. Thus, the files must represent a "portable environment" in and of themselves. This means that any outside dependency must be abstracted away by a command that will download, or import, the dependencies from a source that is accessible from anywhere, anytime, during ANY deployment process. Thus, for every command that will be executed during deployment, a programmer must make sure that the source, the target, and the basis of the relationship between the source and the target are not dependent on any other environment than the one that the programmer wishes to deploy. Also, one must make sure to avoid making environment-dependent assumptions that would cause the deployment to succeed in a specific environment and fail in another.

 

To better understand this first aspect of our definition, let's take the example of the COPY instruction that one might find in a Dockerfile. The problem with this instruction is that one of the terms of this relationship will have to be outside the environment that will be eventually deployed. This will start to get messy when trying to deploy to a production server, or when sharing the deployment file with colleagues through a common code repository. Therefore, any COPY instruction should automatically be replaced by a RUN instruction that will execute a network request to a resource that is accessible from anywhere, anytime, obviously implementing all appropriate security measures. This way, it is possible to avoid ugly dependencies to the developer's initial environment. But, what if we need to import private files into the newly deployed environment? Well, one has many possible ways to resolve this. One might create "fake", or mock files, when deploying to any other environment than production, and allow the real files to be accessed from the production environment only through an authorized API call to some sort of file server, or private code repository. No matter how this is done, the principle behind these examples is to keep the deployment files portable and thus, free of "hard" environment dependencies.

 

The second aspect of our definition is the question of keeping our "portable environment" dependent on text files only. The problem with building Docker environments is the bulky size of the resulting images. Moreover, when building these images on a 'build' or staging server, one must find a way to ship the image to the production environment through some secured channel. This is, most of the time, utterly inefficient, and not very secure. This is made worse by the fact that the 'build' server might be a third-party Continuous Integration (CI) tool that will not keep all of our built images, and will force the build process to start from scratch every time, thus adding even more latency to the entire deployment pipe. By simply transferring text files from one environment to another, one not only reduces network traffic, but also benefits from the presence of the previously built images to build the new one. This is obviously true, only if the new "to be deployed" environment is made totally 'portable' by the deployment configuration files.

 

As an example, let's take the following Dockerfile that I've downloaded from the Internet. You will notice that this file contains a couple of "hard" dependencies to the developer's original environment. The COPY and EXPOSE instructions are going to fail in some environments:

 
FROM php:7
RUN apt-get update -y && apt-get install -y openssl zip unzip git
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN docker-php-ext-install pdo mbstring
WORKDIR /app
COPY . /app
RUN composer install

CMD php artisan serve --host=0.0.0.0 --port=80
EXPOSE 80

If we were to simply change a few lines in order to abstract away these dependencies, anyone could start running this example on their own computer, and get a fully functional Laravel application within seconds.

 
FROM php:7
RUN apt-get update -y && apt-get install -y openssl zip unzip git
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN docker-php-ext-install pdo mbstring
RUN cd / && composer create-project laravel/laravel:~5.4 app
WORKDIR /app
RUN composer install

CMD php artisan serve --host=0.0.0.0 --port=80

Now, in the case of this specific example, there’s an additional issue. The Alpine image is not the best choice when it comes to implementing FBPEs, because the environment is not based on a standard C toolchain, and is prone to fail over time. In this case, the extensions we are trying to compile are expecting the Oniguruma library to be present, which was not the case back when this image was originally built. I will therefore resort to using Linux for PHP images and the Linux for Composer helper library, in order to make my environment completely portable. Here is the new version of my Dockerfile:

 
FROM asclinux/linuxforphp-8.2-ultimate:7.0-nts
RUN composer self-update
RUN cd / && composer create-project laravel/laravel:~5.4 app
WORKDIR /app
RUN composer install

CMD php artisan serve --host=0.0.0.0 --port=80

For the purposes of this article, I have created a working example of the FBPE concept in a GitHub repo that can be found at https://github.com/andrewscaya/laravel_example. You must also have PHP installed and available for the example to work. In order to run this example now, please enter the following commands after entering your favorite directory from your computer’s command-line interface (CLI):

 
git clone https://github.com/andrewscaya/laravel_example
cd laravel_example
php composer.phar self-update
php composer.phar install
vendor/bin/linuxforcomposer.phar docker:run start

After building the image, and running the image as a container, I can finally reproduce the developer's originally intended environment. Go to http://localhost:8181, and you will see that it's as if you were here with me right now! Even if you are re-reading this article a few years later, the self-contained FBPE will still be available for you to test, no matter the hardware or software setup of your computer environment.

 

Over the past year, I've personally reaped the fruits of FBPEs for my company, by seeing many employees, mostly developers, deploy many complex applications, multiple times every day, to multiple environments, without the flicker of an issue. I hope you will too!

 

Loading Conversation