In the previous blog posts we discussed several options on how to run a PHP project in a docker container(s). The first one is about docker compose file, the second is about about Supervisord which runs Nginx and PHP-FPM. We tried to highlight their pros and cons.

In this blog post we show one more solution on how to dockerize a PHP application. The goal is still the same to build a drop in container, you copy our app into it and it just works.

If you prefer to see the code than read, here it is formapro/nginx-php-fpm.

Docker’s best practise is to run a single process inside a container. That’s indeed pretty logical advice but does not work for PHP apps (You can find out why in the previous blog post).

The solution is based on a shell script does all the magic. We tried to pack Nginx, PHP-FPM into a single container (like in Supervisord approach, but without it (: ) with some default configurations.

Here’s how you can run a PHP file behind Nginx and PHP-FPM:

echo "<?php phpinfo();" > app.php docker run -d -p 8080:80 -v `pwd`:/var/www/html formapro/nginx-php-fpm curl -X GET localhost: 8080 # runs app.php and outputs phpinfo

Now let’s see what’s inside. When the container starts it has to run two processes:

# /entrypoint.sh nginx -c /etc/nginx/nginx.conf -g ‘daemon off;’ 2>&1 &

NGINX_PID=$! php-fpm7.0 -R -F -c /etc/php/7.0/fpm/php-fpm.conf 2>&1 &

PHP_FPM_PID=$! wait $NGINX_PID $PHP_FPM_PID

That works but it does not cover some edge cases.

The script does not handle signals correctly. For example if SIGTERM is sent to a container it has to gracefully stop a running process and exit but in our case bash immediately exits. PHP-FPM and Nginx do not have a chance to stop correctly. That could be solved by using trap command which allows to define a script to execute on a signal.

# /entrypoint.sh trap "kill -15 $NGINX_PID; kill -15 $PHP_FPM_PID;" SIGTERM SIGINT

What if one of those processes exits, the container must exit but instead wait command still waits for another process and we end up having half working container. We were able to found the following solution to the problem. We had to replace wait with while loop and some ifs.

# /entrypoint.sh while :

do

kill -0 $NGINX_PID 2> /dev/null

NGINX_STATUS=$? kill -0 $PHP_FPM_PID 2> /dev/null

PHP_FPM_STATUS=$? if [ $NGINX_STATUS -ne 0 ] || [ $PHP_FPM_STATUS -ne 0 ]; then

if [ $NGINX_STATUS -eq 0 ]; then

kill -15 $NGINX_PID;

wait $NGINX_PID;

fi

if [ $PHP_FPM_STATUS -eq 0 ]; then

kill -15 $PHP_FPM_PID;

wait $PHP_FPM_PID;

fi exit 1;

fi sleep 1

done

It checks whether processes are still alive and if one of them has crashed or exited it stops another process.

As we are Symfony developers we’d like to adjust the container to serve Symfony application easily. Here’s example that shows how to change the web root directory:

# /entrypoint.sh export NGINX_WEB_ROOT=${NGINX_WEB_ROOT:-'/var/www/html'}

export NGINX_CONF=${NGINX_CONF:-'/etc/nginx/nginx.conf'} envsubst '${NGINX_WEB_ROOT}' < /tmp/nginx.conf.tpl > $NGINX_CONF nginx -c $NGINX_CONF -g ‘daemon off;’ 2>&1 &

It makes use of handy util called envsubst . It takes a template files with env vars as placeholders and replaces them with their real values. The file stored in a new location. The directory could be customized by adding an env docker option:

docker run -p 80:80 -v `pwd`:/app --env NGINX_WEB_ROOT=/app/web formapro/nginx-php-fpm

The container is present on docker hub. The source code is on github.