Making your dockerised PHP application even better

Before we start…

The previous post about dockerising a PHP application (which you should read if you haven’t) got a lot more attention than I ever imagined, which is AWESOME. However, while introducing some of  the suggested improvements a number discrepencies between the contents of the blog post and the GitHub repository emerged. Buuut…

The death of php-docker.local

The initial setup included a step that required you to update your hosts file and add an entry for php-docker.local. I received feedback from several people that this step is not completely clear and they ended up skipping it. It turns out this step can be removed easily, so why don’t we just go ahead and do it :)

Two things are needed to achieve this. First, we have to update the site.conf in order to handle the connections to localhost. Second, the default configuration in the Nginx image should be replaced with our new config.
For the first part we have to replace the server_name setting with:

    server_name localhost;

and add the following setting:

    listen 80;

Now our site.conf will look like this:

server {
    listen 80;
    index index.php index.html;
    server_name localhost;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /code;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

In order to replace the default Nginx config we have to mount our site.conf in its place. To achieve that we have to tweak our docker-compose.yml a bit. The end result will look like this:

web:
    image: nginx:latest
    ports:
        - "8080:80"
    volumes:
        - ./code:/code
        - ./site.conf:/etc/nginx/conf.d/default.conf
    links:
        - php
php:
    image: php:7-fpm
    volumes:
        - ./code:/code

For those who missed it, we changed one of the volumes for the web container with the following:

        - ./site.conf:/etc/nginx/conf.d/default.conf

Now your PHP application will be accessible on any domain pointing to your Docker host.

Docker-compose v2

For a while now docker-compose supports version 2 for the docker-compose files, which adds some improvements to the setup. Let’s see how the docker-compose.yml will look like using the new format:

version: 2

services:
    web:
        image: nginx:latest
        ports:
            - "8080:80"
        volumes:
            - ./code:/code
            - ./site.conf:/etc/nginx/conf.d/site.conf
    php:
        image: php:7-fpm
        volumes:
            - ./code:/code

This doesn’t look much different, except that we don’t have to specify the links between the containers. Docker-compose adds all the containers to the same network and they are “linked” by default. This is especially useful when you add more containers to the setup (e.g. database, cache, queue, etc.) since you don’t have to worry about specifing the links between containers.

Another thing the we can do using version 2 of the docker-compose files is to specify networks for the containers. For example:

version: 2

services:
    web:
        image: nginx:latest
        ports:
            - "8080:80"
        volumes:
            - ./code:/code
            - ./site.conf:/etc/nginx/conf.d/default.conf
        networks:
            - code-network
    php:
        image: php:fpm
        volumes:
            - ./code:/code
        networks:
            - code-network

networks:
    code-network:
        driver: bridge

This option allows grouping different containers in different networks based on the services they need to connect to. In our setup this is not needed, but I believe it’s an important feature to be aware of, especially when expanding the setup with more services.

Special thanks to cipriantepes, who contributed to the repository with this improvement.

PHP logging to stdout

This is something trivial but I never really thought it’s needed until I got several requests and even a PR for it. The issue is that the default php-fpm Docker image is not configured to log the errors. The fix is to enable logging.

First, let’s add a log.conf file with the following content:

php_admin_flag[log_errors] = on
php_flag[display_errors] = off

Next, add this to the configuration of the PHP container:

 version: '2'

services:
    web:
        image: nginx:latest
        ports:
            - "8080:80"
        volumes:
            - ./code:/code
            - ./site.conf:/etc/nginx/conf.d/default.conf
        networks:
            - code-network
    php:
        image: php:fpm
        volumes:
            - ./code:/code
            - ./log.conf:/usr/local/etc/php-fpm.d/zz-log.conf
        networks:
            - code-network

networks:
    code-network:
        driver: bridge

So… what happened? Well, we mounted our new log file in the PHP container, but we added a zz- prefix to it. Why? Because we want to have this configuration loaded last, so that it’s not overriden by the rest of the configs.
Here’s the line for the curious:

            - ./log.conf:/usr/local/etc/php-fpm.d/zz-log.conf

Since this wasn’t part of the initial blog post, it resides in a separate branch – feature/log-to-stdout.

Final summation

I guess no matter how good you make something, there’s always room for improvement. In that sense, any remarks and PRs are more than welcome :)

 

You can find the updated code here – https://github.com/mikechernev/dockerised-php

Packaging and serving your Java application with Docker

Before we start…

Before we start, we have to agree on one thing – Docker is super cool! Wait… I’ve said this before! And although I’ve also said the next things before, it’s important to note that this will not be an introduction to Docker. I believe there are a lot of articles that can explain the “What?”s and “Why?”s surrounding it. But even though some experience with both Docker and Java is expected, it is not required in order to follow through what we will cover.

The target we’ll be aiming for this time is to compile and package a Java application using Maven and serving the same application with Apache Tomcat without having anything but Docker installed. As we did in Dockerise your PHP application with Nginx and PHP7-FPM, we will use only official Docker Hub repositories. I want stress, again, that this approach lets you run any tool you need in an environment designed by its maintainers and protects you from any image changes that would potentially break your applications (which could happen, and has happened, with community repositories).

The only thing left is to install the Docker Engine, but I hope you have already done this. And even though it is not necessary, you can also install Docker Compose (we will be using it to make things a bit more readable). Now that we have a clear goal and all that we need to accomplish it – lets get this started :)

Setting up Tomcat

Since we want to test our application once we create it, it makes more sense to set up our web server first. As we set in our requirements this will be Apache Tomcat and we want to use the official Docker Hub repository, so the only thing we have to do is run the following command, which will take the official Tomcat image, run it and bind it’s port 8080 to port 8080 of our Docker environment:

docker run -it -p 8080:8080 tomcat

Aaaand that’s it!
Now you should be able to see the Tomcat home page on port 8080 of your Docker environment.

This covers the first part of want we initially set out to do. Let’s see if the second part will be as easy :)

Creating and Packaging a Java application using Maven

Create it!

Now comes the tricky part. If you are familiar with Maven you know that it is a CLI tool, so how do we use it without having Java installed (we agreed on this earlier)? It turns out there is an official Maven image we can use. To check if we can use it let’s run:

docker run --rm -it maven mvn --version

The output you should get is something like this:

Apache Maven 3.3.9 (bb52d8502b132ec0a5a3f4c09453c07478323dc5; 2015-11-10T16:41:47+00:00)
Maven home: /usr/share/maven
Java version: 1.8.0_72-internal, vendor: Oracle Corporation
Java home: /usr/lib/jvm/java-8-openjdk-amd64/jre
Default locale: en, platform encoding: UTF-8
OS name: "linux", version: "4.4.6-moby", arch: "amd64", family: "unix"

Let’s break down the command we just executed. It creates a container and runs the maven command in it. The curious part is the –rm parameter, which will remove the container once the execution of the command is over.

Great! So now that we can run maven in a container let’s create a simple web application. Normally you would do this by running something along the lines of:

mvn archetype:generate \
    -DgroupId=com.mikechernev.docker.example \
    -DartifactId=DockerExample \
    -DarchetypeArtifactId=maven-archetype-webapp \
    -DinteractiveMode=false

If you are not familiar with Maven I suggest you have a look at Maven in 5 Minutes where I took this command from, with the only difference that the architecture type in our case is maven-archetype-webapp. In short, this command will create a new web application project in the DockerExample directory. Knowing how to run maven commands, we can just replace the mvn –version with this command, right? Wrong!
Since the default working directory for the Maven image is /, if we execute:

docker run --rm -it maven mvn archetype:generate \
    -DgroupId=com.mikechernev.docker.example \
    -DartifactId=DockerExample \
    -DarchetypeArtifactId=maven-archetype-webapp \
    -DinteractiveMode=false

it will create the new project in /DockerExample inside the container and this will be forever lost once the container is removed. As I mentioned, because of the –rm parameter, this will happen once the execution of the command ends. To overcome this we have to adjust our command a bit and it will look like this:

docker run --rm -it -v $(pwd):/external -w /external maven mvn archetype:generate \
    -DgroupId=com.mikechernev.docker.example \
    -DartifactId=DockerExample \
    -DarchetypeArtifactId=maven-archetype-webapp \
    -DinteractiveMode=false

If you are familiar with Docker you will notice that we mounted the current directory as /external in the container by using -v $(pwd):/external and we changed the current working dir for the container to /external with -w /external. So now when we run the command we should have the new directory DockerExample created in the mounted volume and preserving it after we destroy the container.

Package it!

Now that we have the project we need to package it. Again, the normal way of doing this is by navigating to the project directory and executing:

mvn package

We want to run this inside a container, thus we need to mount the folder it is in as a container volume. But first lets navigate to the location of our project, in my case I just go into the DockerTest directory. Since Maven lets us specify the location of the project we have two options to package the project. The first one is to set the working directory the same way we did when we created the new application, using Docker’s -w parameter:

docker run --rm -it -v $(pwd):/project -w /project maven mvn package

The second option is to run the command using Maven’s -f parameter to specify the location of the pom.xml and it looks like this:

docker run --rm -it -v $(pwd):/project maven mvn package -f /project

Both commands should create a directory called target in the location of your project and it should contain your build. Since we set the artefact ID to be DockerExample it should be something like this:

where the DockerExample and DockerExpanded.war are the expanded and archived builds.
And again – this is IT! This is all it takes to package your Java application, easy right? Right!

Now that we have the application is ready let’s serve it with the web server we prepared.

Deploying and serving your Java application

Before we get any further I want to clarify something that puzzled me a lot when I started using Tomcat and that is the app deployment. If you are familiar with Tomcat you can skip to the next paragraph, otherwise – BEHOLD! The way you deploy an application to Tomcat is by simply adding your application build to Tomcat’s webapps directory (in the case of the  official Docker image that is /usr/local/tomcat/webapps). And here’s the tricky part – based on the build name added to the webapps directory Tomcat maps this application to a specific route. So if we add our DockerExample.war to the webapps as docker-example.war, we will be able to access it on /docker-example path for the Tomcat url. If you want to deploy your application without any extra paths, you have to place an artefact named ROOT. If this still sounds vague I am sure everything will be clear by the end of this blog post.

Let’s get this started!
If you navigate to your application directory you should be able to serve your application using:

docker run -it \
    -p 8080:8080 \
    -v $(pwd)/target/DockerExample.war:/usr/local/tomcat/webapps/docker-example.war \
    tomcat

Now if we go to /docker-example on port 8080 for your Docker environment you should be able to the see the output of our basic web application:

But what if we want this to be the our website instead of just one of the many endpoints. As we said earlier, Tomcat requires a ROOT application for that. We can achieve this by modifying our Docker command to look like this:

docker run -it \
    -p 8080:8080 \
    -v $(pwd)/target/DockerExample.war:/usr/local/tomcat/webapps/ROOT.war \
    tomcat

This should do the trick, but instead gives me the opportunity to use my favourite gif again:

It turns out that the Tomcat image has an exploded artifact deployed as ROOT, so we have to modify our command a bit and it will look like this:

docker run -it \
    -p 8080:8080 \
    -v $(pwd)/target/DockerExample.war:/usr/local/tomcat/webapps/ROOT.war \
    -v $(pwd)/target/DockerExample:/usr/local/tomcat/webapps/ROOT \
    tomcat

And now you should be getting the proper response:

And our job here is done… except for a couple of finishing touches.

Compose it!

This part is not really necessary, but if you have Docker Compose installed you could serve the application by using the following docker-compose.yml:

version: '2'
services:
  web:
    image: tomcat
    ports: 
      - "8080:8080"
    volumes:
      - ./target/DockerExample.war:/usr/local/tomcat/webapps/ROOT.war
      - ./target/DockerExample:/usr/local/tomcat/webapps/ROOT

Run it!

Once you have this you can create a simple shell script to package and serve your application.
run.sh:

docker run --rm -it -v $(pwd):/project -w /project maven mvn package && docker-compose up

This is it!
We can now create, build and serve our Maven application, using only the official Docker images for Maven and Tomcat.

You can find the source code for this post here https://github.com/mikechernev/dockerised-java

P.S.: Not great for development, perfect for testing things out

As you can imagine this setup is not going to be very useful for local development, since you don’t have an easy way to debug your application (or I still haven’t figured out one). But this is a great way to quickly package and serve your application on a VPS, which is what I initially did this for. It is also a fast and easy way to test out new applications on a clean environment. That’s why I won’t suggest using this setup for local development, but I highly recommend it for deploying your application.

 

Dockerise your PHP application with Nginx and PHP7-FPM

Before we start…

Before we start, we have to agree on one thing – Docker is super cool! If you are not familiar with Docker, I suggest to have a look at the tons of “Getting starting with Docker” or “What is Docker?” articles and then come back here. :)
Since you keep reading, I will assume that you already have some Docker experience and you want to run your PHP applications in containers. Because who wants the trouble of installing all the dependencies on their local environment  or manage a number of virtual machines for their different projects, right? Right!

The goal that we will try to achieve is to run a simple PHP application using the official Docker repositories for both PHP and Nginx. There are several docker repositories combining PHP-FPM with Nginx, but depending on the official repositories gives you several benefits, like using a service which is configured by its maintainers and you can always choose between the latest and greatest or different versions of both services, instead of relying on someone else’s choices.

The first thing you have to do is, of course, install Docker (if you haven’t already). The second prerequisite is getting Docker Compose (it is included in the Mac toolbox).  Now that we know what we want to achieve and have the tools to accomplish it – let’s get our hands dirty!

Setting up Nginx

We’ll start by getting ourselves a web server and based on our requirements this will be a container running the official Nginx image. Since we’ll be using Docker Compose, we will create the following docker-compose.yml file, which will run the latest Nginx image and will expose its port 80 to port 8080:

web:
 image: nginx:latest
 ports:
 - "8080:80"

Now we can run

docker-compose up

This should give you the default Nginx screen on port 8080 for localhost or the IP of your docker machine.

Now that we have a server let’s add some code. First we have to update the docker-compose.yml to mount a local directory. I will use a folder called code, which is in the same directory as my docker-compose.yml file, and it will be mounted as root folder code in the container.

web:
    image: nginx:latest
    ports:
        - "8080:80"
    volumes:
        - ./code:/code

The next step is to let Nginx know that this folder exists.
Let’s create the following site.conf on the same level as the docker-compose.yml file:

server {
    index index.html;
    server_name php-docker.local;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /code;
}

If you don’t have a lot of experience with Nginx, this is what we define here – index.html will be our default index, the server name is php-docker.local and it should be pointing (update your hosts file) to your Docker environment (localhost if you are on Linux or the docker machine if you are on Mac or Windows), we point the error logs to be the ones exposed by the default container, so that we will see the errors in our docker compose log, and finally we specify the root folder to be the one that we mounted in the container.

To activate this setup we need to apply yet another modification to our docker-compose.yml file:

web:
    image: nginx:latest
    ports:
        - "8080:80"
    volumes:
        - ./code:/code
        - ./site.conf:/etc/nginx/conf.d/site.conf

This will add site.conf to the directory where Nginx is looking for configuration files to include. You can now place an index.html file in the code folder with contents that is to your heart’s delight. And if we run

docker-compose up

again, the index.html file should be available on php-docker.local:8080.

Yeey! We are half way there

Adding PHP-FPM

Now that we have Nginx up and running let’s add the PHP in the game. The first thing we’ll do is pull the official PHP7-FPM repo and link it to our Nginx container. Our docker-compose.yml will look like this now:

web:
    image: nginx:latest
    ports:
        - "8080:80"
    volumes:
        - ./code:/code
        - ./site.conf:/etc/nginx/conf.d/site.conf
    links:
        - php
php:
    image: php:7-fpm

The next thing to do is configure Nginx to use the PHP-FPM container for interpreting PHP files. Your updated site.conf should look like this:

server {
    index index.php index.html;
    server_name php-docker.local;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /code;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

In order to test this let’s rename the index.html file to index.php and replace its content with the standard:

<?php
echo phpinfo();

One final

docker-compose up

And we should be good to go... but

Instead of getting the proper PHP info page we receive the rather unsettling

File not found.

Since PHP is running in its own environment (container) it doesn't have access to the code. In order to fix this, we need to mount the code folder in the PHP container too. This way Nginx will be able to serve any static files, and PHP will be able to find the files it has to interpret. One final change to the docker-compose.yml:

web:
    image: nginx:latest
    ports:
        - "8080:80"
    volumes:
        - ./code:/code
        - ./site.conf:/etc/nginx/conf.d/site.conf
    links:
        - php
php:
    image: php:7-fpm
    volumes:
        - ./code:/code

Finally, this last (this time for real)

docker-compose up

present us with the much wanted PHP info

This is it.

We can run any simple PHP application inside Docker containers, using the official images for Nginx and PHP.

You can find the sample project here https://github.com/mikechernev/dockerised-php

EDIT: Since the GitHub repository changed quite a lot, I added a new blog post explaining the improvements - Making your dockerised PHP application even better