Running nginx and php-fpm in docker and avoid the bad gateway problem

One of the most used container images on sloppy.io is nginx an open source reverse proxy server which can be used for a lot of scenarios. A popular one is to use nginx in combination with php-fpm where nginx is forwarding requests to php files to the php-fpm container and then delivers the output back to the client. Let’s check what a setup like this needs.

  1. nginx container with a custom default.conf and php file
  2. php:fpm container with a php file

 

For our nginx container we create a simple index.php

<html>
<head>
<title>PHP-Example</title>
</head>
<body>
<?php echo '<p>Hello from sloppy.io</p>'; ?>
</body>
</html>

and a default.conf

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.web.ng-fpm.mikemichel811.node.intern:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

The fastcgi_pass is the part which tells nginx to which hostname it should forward requests to php files. Here it is php.web.ng-fpm.yourusername.node.intern:9000 which is the FQDN of the php:fpm container we will create later. The name is a combination of appname.servicename.projectname.youruserid.node.intern as this is the way you connect containers with sloppy.io. 9000 is the port php:fpm is listening on.

If you want to deploy this example too you need to change at least the userid part to your own userid which you can find here https://admin.sloppy.io/account/profile

Now a Dockerfile to build our nginx container:


FROM nginx:1.15
COPY default.conf /etc/nginx/conf.d/default.conf
COPY index.php /code/index.php

Build it: docker build -t yourregistryaccount/nginx:php-fpm .
Push it: docker push yourregistryaccount/nginx:php-fpm

Now the php:fpm container. If you have all files in the same directory like me create a file called Dockerfile-php.

FROM php:fpm
COPY index.php /code/index.php

Build it: docker build -t yourregistryaccount/php -f Dockerfile-php .
Push it: docker push yourregistryaccount/php

 

We are ready to deploy it to sloppy.io. Create a new project with the following structure. Remember the naming of the hostname in default.conf? This is now important when giving the project, service, and app a name.

For the nginx container you just configure a domain of your choice

 

while the php container does not need a network to the outside world.

 

Deploy it and test it by opening the free *.sloppy.zone domain you configured in the domain field. All good, right? Yes, until you deploy a new version of your php container or just restart it.

Ups, what’s that? Let’s check the logs.

nginx can not connect to the php container anymore. In dynamic container environments like sloppy.io a container will get a new internal IP address after every restart. This should not be a problem as we use a hostname in default.conf, right? Wrong! nginx resolves this hostname only 1 time while it starts up. It will not recognize the change of the IP address so restarting nginx will solve this but is there a better solution? Well, nginx “plus” the commercial version of nginx can handle dynamic upstreams if you can handle the pricing.  But there is another way.

Change the default.conf

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$ {
	    resolver 172.17.0.1;
		set $upstream_endpoint php.web.ng-fpm.youruserid.node.intern;
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass $upstream_endpoint:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

We use the nginx resolver directive (172.17.0.1 is the default IP the docker daemon is running on).
When proxy_pass command is getting $variable (here we name it $upstream_endpoint) instead of the URI, it uses the DNS resolver in case the cache entry for the IP has expired. This way nginx is always aware of the current IP of our php-fpm container. Build and push the nginx container again:

Build it: docker build -t yourregistryaccount/nginx:php-fpm .
Push it: docker push yourregistryaccount/nginx:php-fpm

Restart the nginx container in the sloppy.io user interface. The new image will be started and now if we restart or deploy a new version of our php code we will not see the gateway error again.

Don’t have a sloppy.io account yet? Sign up now, it’s free.