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
This article is pretty much just about how to run php and nginx apps locally. How do you build images and deploy them to other environment. When Im using docker I dont want to put source code on the host instances. I want to run containers and thats it.
Using snippets of code here, I built a LEMP stack and deployed that to my swarm. To preserve the data, and ensure that all nodes can run the containers, I gave all the nodes access to a central datastore.
May be there are more than one way of dockerizing php applications. I followed a tutorial to dockerize php application (https://www.cloudways.com/blog/docker-php-application/ ). In this tutorial, the docker compose file had php and db defined. The dockerfile for these two were also created. But in your tutorial there is no mention of that.
If I follow ./site.conf:/etc/nginx/conf.d/site.conf it will not work, but if I changed to ./site.conf:/etc/nginx/conf.d/default.conf then it will work and I use image: php:7.2-fpm
Same for me, it only works with ./site.conf:/etc/nginx/conf.d/default.conf
That’s because you need to request the virtual host of “php-docker.local” (as in the example) as set in site.conf: server_name
You won’t find it because you’re hitting the default nginx host, not the named virtual host.
Thanks a lot for this post. It`s help me to crate high load website in docker swarm.
Here is a link https://www.linkedin.com/pulse/nginx-high-load-website-expandable-docker-swarm-vladimir-izmalkov/ to instruction how to create stack with Nginx for high load.
When i follow all of your steps, i gninx always opens the default nginx html (Welcomet to nginx etc.). It seems that my configuration with the code directory does not get into count when building with docker-compose.
Can you help me with that?
See the reply from Sony AK. Change
./config/site.conf:/etc/nginx/conf.d/site.conf
to
./config/site.conf:/etc/nginx/conf.d/default.conf
That fixed the same issue for me.
Thank you! Helped me out :)
what if my code is in the same directory as docker-compose.yml? where should code be?
If I use the volume as defined: ./site.conf:/etc/nginx/conf.d/site.conf I get an error as @SonyAK mentions. However, if I change to ./config/site.conf:/etc/nginx/conf.d/default.conf like mentioned, I get an different error:
ERROR: for f56b8ca52bb8_build_web_1 Cannot start service web: b’OCI runtime create failed: container_linux.go:348: starting container process caused “process_linux.go:402: container init caused \\”rootfs_linux.go:58: mounting \\\\\\”/Users/jeremymcjunkin/Dropbox/workspaces/doapp/feed-cleaning/build/site.conf\\\\\\” to rootfs \\\\\\”/var/lib/docker/overlay2/6916bf26b4516db40db3c3e4db8059541db5cc423caa774965845271d9f33186/merged\\\\\\” at \\\\\\”/var/lib/docker/overlay2/6916bf26b4516db40db3c3e4db8059541db5cc423caa774965845271d9f33186/merged/etc/nginx/conf.d/default.conf\\\\\\” caused \\\\\\”not a directory\\\\\\”\\””: unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type’
Hmm, it looks like the volume path is relative to the location of the docker-compose file. I had the docker-compose file in a build/ directory, that is why I had the issue.
This is my folder structure
proj
– code
– docker-compose.yml
– site.conf
What are the necessary changes i have to do to overcome the above issue?
Hello,
Thank you for this helpfull topic!!
Really great article!
Can you please add in this tutorial the way to add a mysql server too.
Thanks a lot!
I agree, information how connet with mysql will be very helpfull.
I replaced simple strings for passwords. Else use env variables for group projects
“`
version: “3”
services:
web:
image: nginx:latest
ports:
– “9999:80”
volumes:
– ./code:/code
– ./site.conf:/etc/nginx/conf.d/site.conf
links:
– php
php:
# image: php:7-fpm-stretch
build:
context: .
dockerfile: “dockerfiles/php/Dockerfile”
volumes:
– ./code:/code
mysql:
image: mysql:8.0
container_name: mysql-server-80
command: –default-authentication-plugin=mysql_native_password
volumes:
– ./code:/code
restart: always
environment:
– MYSQL_ROOT_PASSWORD=.pass.
– MYSQL_DATABASE=my_db
– MYSQL_USER=db_user
– MYSQL_PASSWORD=.pass
ports:
– “8082:3306”
“`
The dockerfile I reference for the PHP build phase looks like:
“`
FROM php:7-fpm
WORKDIR ‘/code’
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
“`
Alright, that got butchered. I hope you get the idea. Use an online yaml linter if needed to make sure you get the right denting. This is just adapted from the OP in this article, so it should work.
Happy keystroking!
Very Good!
Thanx. Try to use it on forecasting servers. But there to much very specific software (
nice guide, however i got 403 forbidden error with index.php
Had the same problem, then I realised my index.html file wasn’t in the code directory.
You have to check the file permissions of your code directory on the machine hosting docker. It could be getting a “Permission Denied” error. It would say so in your logs.
This is by far the most clear and to the point guide on php and docker.
Thx buddy.
Idk y when I put my site.conf in the conf.d folder it does work. It also should work if you put the file inside the site-enabled, but it doesn’t.
Anyway, thank you so much, it helped me a lot. I am still understanding how docker works.
cos the virtual host in the example is docker.local, so unless you’re accessing http://docker.local:8080 (you’ll have to set this up in your /etc/hosts) you’re going to get parsed by the default host, rather than the virtual host for docker.local as set up in site.conf
I assume you’re trying the example as http://localhost:8080 when this fails.
Thank you for imagine topic.
This thing is driving me crazy. I followed the instruction and if I visit http://localhost:8080 I still get the nginx welcome page. If I visite http://php-docker.local:8080 it loads for few seconds and then it returns a “Site can’t be reached”. The fact that is not failing immediately, is pretty suspicious.
You have to do it like this now.
version: ‘3’
services:
web:
image: nginx:alpine
ports:
– “8090:80”
links:
– api
volumes:
– www-data:/var/www/html
– ./site.conf:/etc/nginx/conf.d/site.conf
api:
image: php:fpm-alpine
volumes:
– www-data:/var/www/html
volumes:
www-data:
Notice the last part for *volumes*. It’s a shared name between the two containers.
Valeu cara!
This would be better without the memes
Help
Error – could not build server_names_hash, you should increase server_names_hash_bucket_size: 32
Hey, great article. I have 3 questions left though.
Would be nice if somebody could answer them. :-)
1) I can only make it work, when I put rename the site.conf -> nginx.conf and place it inside
/etc/nginx/
Anyone an idea, why that is?
2) Inside docker-compose-yml
To we need to specifiy
links:
– php
Since we did not specify the bride drivers for the network,
docker-compose will create a network based on the location name of the file.
Consequently, – unlike inside the bridge network – a DNS server is installed.
Would not it make the links: unnecessary?
3)
I know that include_param is similiar to mime.types to interpret file types correctly inside the package header. However, what are
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
for? Again, nice post.