Nginx and PHP-FPM Running with Docker

Posted in How-to

by digitalap3 on Wednesday 30th September 2020

A Long Standing Problem

For whatever reason, most likely a combination of poor google skills and use cases that are outside of most needs and I just didn't need it that badly, I have had trouble getting the php-fpm docker image to work with an nginx container. Finding no explicit directions, I was finally able to figure it out. The most likely reason is that it's rather obvious to more experienced users of php.

...
from Keep Calm

Whatever the reason I give you explicit (though very safe for work) instructions for getting an nginx and php-fpm container to work together.

First we will pull the images so we can take some necessary files out of them.

Nginx

Pull the nginx image with the command docker pull nginx. Since we will be using the html directory and configuration directory external to the container we will pull the files from there first so they can be placed in the external directories (or volumes if that's your thing):

docker run --rm -d nginx ## pull image and spin up container

docker ps ## make note of the name of container

docker exec -i <container-name> ls /user/share/nginx/html ## check for the files we want

docker cp  <container-name>:/user/share/nginx/html . ## creates directory html with files

docker cp <container-name>:/etc/nginx/conf.d/default.conf .

...


Now build the image we will be using. This is the Dockerfile I use which as you can see adds some applications. We will expand this in a later post to include those necessary for using letsencrypt and certbot.

FROM nginx

RUN apt-get update && DEBIAN_FRONTEND=noninteractive \
    apt-get -y upgrade && \
    apt-get -y --no-install-recommends install \
    apache2-utils curl vim less procps inotify-tools \

VOLUME /etc/nginx/conf.d/
VOLUME /usr/share/nginx/html/

And using this file we will build our own nginx image with docker build -t mynginx . (don't forget the period at the end!)

PHP-FPM

...


Next pull the php-fpm image with the command docker pull php:7.3-fpm . We are going to need to make changes to key files and then build them back in so it is best to do all this in an empty directory:

docker run --rm -d php ## spin up a container

docker ps ## check the name

docker cp <container-name>:/usr/local/etc/php/php.ini-production php.ini  ## get php.ini-production and change the filename

docker cp <container-name>:/usr/local/etc/php-fpm.d/www.conf . ## get www.conf

Now to make the necessary changes to www.conf so that php runs as nginx and is able to write to nginx's html directory:

sed -i 's/owner = www-data/owner = nginx/g' www.conf
sed -i 's/group = www-data/group = nginx/g' www.conf

We will create this user when we build the image.

Now to make changes to the php.ini file:

## this is required
sed -i 's/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g' php.ini

## these are optional, but required for the example and many use cases.
sed -i 's/post_max_size = 8M/post_max_size = 200M/g' php.ini

sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 200M/g' php.ini

And now to build the image with this dockerfile:

FROM php:7.3-fpm

RUN useradd  -s /bin/bash nginx && \
    usermod -u 101 nginx && \
    groupmod -g 101 nginx

COPY www.conf /usr/local/etc/php-fpm.d/www.conf
COPY php.ini /usr/local/etc/php/php.ini

and the command docker build -t myphp . to build our personal image.

Now that we have our own images, let's spin it all up!

Using Docker-Compose to Make It Happen

...


For my some containers, including nginx, I use persistent directories mounted to host directories. I put into a .env file the path for the uppermost docker volume directory (docker-compose automatically looks for this file):

DOCKERDIR=/path/to/host-mounted/docker/persistent-volumes

The compose file looks like this:

version: '3'

services:

  myphp:
    image: myphp
    container_name: myphp
    volumes:
      - "$DOCKERDIR/nginx/html:/usr/share/nginx/html"
    expose:
      - "9000"
    networks:
      - myinternal

  mynginx:
    image: mynginx
    container_name: mynginx
    volumes:
      - "$DOCKERDIR/mynginx/config:/etc/nginx/conf.d"
      - "$DOCKERDIR/mynginx/html:/usr/share/nginx/html"
    ports:
      - "80:8080"
      - "443:4443"
    networks:
      - myexternal
      - myinternal

networks:
  myexternal:
    external: true
  myinternal:
    external:true

Take note that both containers explicitly share an html directory and network, which can be created with the command docker network create myexternal and docker network create myinternal.

Next I recommend using something like tmux or screen so that the process we start can continue to run in the background. Once you are in the new shell use docker-compose up for a verbose startup that continues to show a running output.

Now let's create our phpinfo.php file in the same directory as the index.html we pulled from the nginx image earlier with the following text so we can test our containers:

    <?php
    phpinfo();
    ?>

We also need to change our default.conf file to modify the listening port and direct nginx to use our php container:

server {
    listen       8080;
    server_name  _;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    root   /usr/share/nginx/html;

    location / {
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        fastcgi_pass   myphp:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        include        fastcgi_params;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

Changes made:

  • the listening port

  • the root location is moved out of the location block so it applies to all locations

  • the php server section is uncommented

  • 127.0.0.1 is changed to myphp.

We will now move these files to their new homes. We need to be root to do this since those directories, which docker created when the containers were spun up with compose, are owned by user-101 / nginx:

su ## or sudo -i or sudo or ...
cp default.conf DOCKERDIR/mynginx/conf/
cp ./html/* DOCKERDIR/mynginx/html/
chown  -R 101:101 DOCKERDIR/mynginx

Finally we need to restart nginx with the new default.conf:

docker exec -i mynginx nginx -t ## confirm there are no problems with the file
docker exec -i mynginx nginx -s reload

You should now be able to go to yourdomain.com/phpinfo.php and see the php information table. Yay!

Sources:

  1. Serve PHP with PHP-FPM and NGINX

  2. How to Solve “No input file specified” with PHP and Nginx

  3. This serverfault answer about locating relevant files


Comments

Feel free to make up a username and log in anonymously to comment