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
not working
It works! Don’t forget to add /code to php section
I’ve replaced the code but it keeps showing me the Welcome to NGINX default page and not my code
remove the /etc/nginx/conf.d/default.conf file.
Possibly your site is not really named “php-docker.local”? The NGinx configuration shown here requires the host header set to the server_name value. So if you’re trying to access your docker site using localhost, you should change the server_name parameter to contain “localhost”.
this was the problem for me. I changed it and it worked great
Hi, I have a question regarding the permissions in the /code directory;
The NGINX and the PHP-FPM are running with different users – Nginx is running with NGINX user and PHP-FPM is running wiht www-data;
so question 1:
should we run PHP-FPM and NGINX with the same Users and Groups/UIDs and GUIDs;
question 2:
so what permissions should the directory /code and files in it should have?
I am asking this because i am going to run this in kuberenetes with a shared volume :)
Thank for working
Great post, thank you. Could you give a hint how to install php extensions?
Why we need add volume code to nginx and php? I thought it is enough to include it to php container
NGINX needs it to serve non-php files. Only php-files are handed to the php-container.
The php-container needs the volume for serving the php-files to nginx.
Could you elaborate on this?
I’ve never seen before that with the use of nginx you’d need to include the actual code within the nginx container. Nginx by itself should be able to route the requests to their correct places.
So is it in fact fastCGI that requires access to the files? And if so , do you have any idea why and whether this can be overcome or not?
I just can’t understand why it would be necessary to have the code exist in both of the containers, it just seems so wrong. I have a PHP microservice application that I am right now trying to dockerize but there has to be a workaround to copying all of the code to both the php-fpm and nginx containers.
So, as Benjamin said, the nginx container needs to be able to serve the images, css etc. Only the php requests are sent to the php container, but nginx still has to be able to return everything else to the end user.
nginx can be configured as nothing more than a reverse proxy, in which case it doesn’t need access to any web files, but in this case, it is not behaving as a web server.
You have to have a web server providing the static files, in addition to the php container.
An alternative setup, would be to install nginx and php within the same container. But this defeats the purpose of separating out the processes.
You could mount the volume inside the nginx container from the php one. But it depends on what you prefer
Hi everybody
I’ve been reading quite a bit about scaling services with Nginx and PHP. The traditional architecture of containers is to have an Nginx container and a PHP container, both linked in the Nginx configuration file, in this file nginx gets php support by calling the service directly in “fastcgi_pass php: 9000;”.
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;
}
So far no problem.
But what happens if we scale this in a SWARM to 20 NGINX containers and 20 PHP: I have found that there is no persistence between containers, I have persistence with the nginx container that serves the page, but no persistence between the Nginx container and the container that supports PHP.
This becomes a problem for the handling of PHP sessions, although I have persistence with the Nginx container through Traefik, I do not have persistence with the container where I have the PHP session since SWARM always uses the PHP container that have less work or by the algorithm to distribute loads.
I don’t know if there is any way to have persistence between these containers or I just have to put my architecture together differently and have only one container that gives me both NGinx and PHP services and not in separate containers. I could also have only one PHP container serving all of the Nginx containers, which would perhaps be a bottleneck for me or not give me a good performance.
I am setting up my swarm and I have not been able to find in the literature what is the best way to approach this issue of sessions in PHP and nginx in a cluster.
Regards.
Although this comment has been made very long before. I will answer this for the future readers. You can use redis or memcache to store sessions instead of relying on file-based sessions.
Possibly your site is not really named “php-docker.local”? The NGinx configuration shown here requires the host header set to the server_name value. So if you’re trying to access your docker site using localhost, you should change the server_name parameter to contain “localhost”.
It works for me:
docker-compose.yml
version: “3.3”
services:
web:
image: nginx:latest
container_name: web
ports:
– “8080:80”
volumes:
– /root/docker/code:/codie
– /root/docker/site.conf:/etc/nginx/conf.d/default.conf
php:
image: php:7-fpm
container_name: php
volumes:
– /root/docker/minipetct.hu/code:/code
site.conf:
server {
index index.php;
index index.html;
server_name php-docker.local;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /code;
# PHP-FPM Configuration Nginx
location ~ \.php$ {
try_files $uri = 404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
Important lines in docker-compose.yml:
– /root/docker/site.conf:/etc/nginx/conf.d/default.conf
and in site.conf:
index index.php;
index index.html;
Important lines in docker-compose.yml:
– /root/docker/site.conf:/etc/nginx/conf.d/default.conf
This is a Very Important Modification because in article this is defined wrong
sorry in volumes of docker-compose.yml
– /root/docker/code:/codie
must be replaced by
– /root/docker/code:/code
Thank you ! You are awesome !
Hello. https://jayrims.co.uk/
I’m getting a 403 Forbidden when I use index.php. Works for index.html
I had the same issue, I had missed this line in the site.conf
server {
index index.php index.html;
….
Err “primary script unknown”
in nginx default.conf change: (default full path in image php)
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
to
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name;
it works for me.
docker-compose
nginx:
image: nginx:1.19.6
ports:
– “80:80”
volumes:
– ./nginx:/etc/nginx/conf.d
– ./html:/usr/share/nginx/html
links:
– php
php:
image: php:8.0.1-fpm-buster
volumes:
– ./php:/usr/local/etc/php/conf.d
– ./html:/var/www/html
Yo man I have the same mistake, could you explain what workaround you found please ?
God bless you. Saved my time a lot
Still doesn’t work. I still get that File Not Found. here’s mine:
php:
build:
context: docker/php
dockerfile: Dockerfile
args:
uid: 1000
container_name: php
restart: always
volumes:
– ./server:/var/www/html/server
environment:
# If you down want to use xDebug, set remote_enable=0
XDEBUG_CONFIG: “remote_enable=0”
PHP_IDE_CONFIG: “serverName=Docker”
Terrific tutorial, helped me understand Docker in a simple step by step way. Thanks so much
Easiest way I found was via https://phpdocker.io
FWIW the website marcit.eu may have done some – alleged – plagia of your article. It’s not word-for-word of course but it seems like they copy-pasted your article and changed the phrasing a bit. Don’t know if that counts or something, wanted to let you know just in case it does.
Thanks you are only that help me
very good tutorial but i got this error :
Creating blog_php_1 … done
Recreating blog_web_1 … error
ERROR: for blog_web_1 Cannot start service web: Cannot link to a non running container: /blog_php_1 AS /blog_web_1/blog_php_1
ERROR: for web Cannot start service web: Cannot link to a non running container: /blog_php_1 AS /blog_web_1/blog_php_1
how can i fix this?