nginx.org.
Commercial support is available at
nginx.com.
Thank you for using nginx.
Looks good! Now let's customize some stuff: - point the root to /var/www
- place a "Hello world" index file in /var/www/index.html
sed -i "s#/usr/share/nginx/html#/var/www#" /etc/nginx/conf.d/default.conf
mkdir -p /var/www
echo "Hello world!" > /var/www/index.html
To make the changes become effective, we need to reload nginx via
nginx -s reload
[email protected]:/# nginx -s reload
2018/05/29 09:22:54 [notice] 351#351: signal process started
Check with curl, et voilá:
[email protected]:/# curl 127.0.0.1:80
Hello world!
With all that new information we can set up our nginx image with the following folder structure on the host machine:
C:\codebase\docker-php
+ nginx\
+ conf.d\
- site.conf
- Dockerfile
+ app\
- index.html
- hello-world.php
nginx\Dockerfile
FROM nginx:latest
nginx\conf.d\site.conf
server {
listen 80;
server_name localhost;
root /var/www;
}
app\index.html
Hello World
Clean up the "exploration" nginx container, cd
into /c/codebase/docker-php/nginx
and build the new image:
docker rm -f $[docker ps -aq]
cd /c/codebase/docker-php/nginx
docker build -t docker-nginx-image .
[email protected] MINGW64 /c/codebase/docker-php
$ docker rm -f $[docker ps -aq]
15c6b8d8a2bf
[email protected] MINGW64 /c/codebase/docker-php
$ cd nginx
[email protected] MINGW64 /c/codebase/docker-php/nginx
$ docker build -t docker-nginx-image .
Sending build context to Docker daemon 3.584kB
Step 1/1 : FROM nginx:latest
---> ae513a47849c
Successfully built ae513a47849c
Successfully tagged docker-nginx-image:latest
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
And then run the "new" container via
docker run -di --name docker-nginx -p 8080:80 -v "C:\codebase\docker-php\nginx\conf.d":/etc/nginx/conf.d/ -v "C:\codebase\docker-php\app":/var/www docker-nginx-image
where
-p 8080:80 // maps port 8080 on the windows host to port 80 in the container
-v "C:\codebase\docker-php\nginx\conf.d":/etc/nginx/conf.d/ // mounts the conf.d folder on the host to the correct directory in the container
-v "C:\codebase\docker-php\app":/var/www // mounts the "code" directory in the correct place
Thanks to the port mapping we can now simply open //127.0.0.1:8080/ in a browser on the host machine and see the content of our app\index.html
file.
If you want some more information about running nginx on Docker, check out this tutorial.
Before we move on, let's clean up
docker stop docker-nginx
Setting up php-fpm
We are already familiar with the official docker PHP image but have only used the cli-only version so far. FPM ones can be pulled in by using the -fpm
tags [e.g. like php:7.0-fpm
]. As with nginx, let's explore the php-fpm image first:
docker run -di --name php-fpm-test php:7.0-fpm
The first thing to note is, that the image automatically exposes port 9000 as a docker ps
reveals:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c5d23b694563 php:7.0-fpm "docker-php-entrypoi…" 4 hours ago Up 4 hours 9000/tcp php-fpm-test
When we examine the
Dockerfile that was used to build the image [click here and search for the "7.0-fpm" tag that currently [2018-05-31] links here], we can see that it contains an EXPOSE 9000
at the bottom.
What else we can we figure out...
winpty docker exec -it php-fpm-test bash
First, will check where the configuration files are
located via php-fpm -i | grep config
:
[email protected]:/var/www/html# php-fpm -i | grep config
Configure Command => './configure' '--build=x86_64-linux-gnu' '--with-config-file-path=/usr/local/etc/php' '--with-config-file-scan-dir=/usr/local/etc/php/conf.d' '--enable-option-checking=fatal' '--disable-c
gi' '--with-mhash' '--enable-ftp' '--enable-mbstring' '--enable-mysqlnd' '--with-curl' '--with-libedit' '--with-openssl' '--with-zlib' '--with-libdir=lib/x86_64-linux-gnu' '--enable-fpm' '--with-fpm-user=www-da
ta' '--with-fpm-group=www-data' 'build_alias=x86_64-linux-gnu'
fpm.config => no value => no value
[...]
--with-config-file-path=/usr/local/etc/php
is our suspect. So it is very likely, that we will find the global directives config file at /usr/local/etc/php-fpm.conf
[unfortunately, we cannot resolve the location directly]. grep
'ing this file for include=
reveals the location for the pool directives config:
grep "include=" /usr/local/etc/php-fpm.conf
[email protected]:/var/www/html# grep "include=" /usr/local/etc/php-fpm.conf
include=etc/php-fpm.d/*.conf
Hm - a relative path. That looks kinda odd? Let's get a little more context with the -C
option for grep
:
grep -C 6 "include=" /usr/local/etc/php-fpm.conf
[email protected]:/var/www/html# grep -C 6 "include=" /usr/local/etc/php-fpm.conf
; Include one or more files. If glob[3] exists, it is used to include a bunch of
; files from a glob[3] pattern. This directive can be used everywhere in the
; file.
; Relative path can also be used. They will be prefixed by:
; - the global prefix if it's been set [-p argument]
; - /usr/local otherwise
include=etc/php-fpm.d/*.conf
Ah - that makes more sense. So we need to resolve etc/php-fpm.d/*.conf
relative to /usr/local
. Resulting in /usr/local/etc/php-fpm.d/*.conf
[usually you'll at least find a www.conf
file in there]. The pool config determines amongst other things how php-fpm listens for connections [e.g. via Unix socket or via TCP IP:port].
cat /usr/local/etc/php-fpm.d/www.conf
[email protected]:/var/www/html# cat /usr/local/etc/php-fpm.d/www.conf
[...]
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
; a specific port;
; 'port' - to listen on a TCP socket to all addresses
; [IPv6 and IPv4-mapped] on a specific port;
; '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = 127.0.0.1:9000
[...]
php-fpm ist listening on port 9000 on 127.0.0.1 [localhost]. So it makes total sense to expose port 9000.
Installing xdebug
Since we probably also want to debug php-fpm, xdebug needs to be setup as well. The process is pretty much the same as for the cli image:
pecl install xdebug-2.6.0
docker-php-ext-enable xdebug
php-fpm -m | grep xdebug
Of course we'll also put that in its own Dockerfile:
C:\codebase\docker-php
+ php-fpm\
- Dockerfile
php-fpm\Dockerfile
FROM php:7.0-fpm
RUN pecl install xdebug-2.6.0 \
&& docker-php-ext-enable xdebug
Clean up the test container and build the new image
docker rm -f php-fpm-test
cd /c/codebase/docker-php/php-fpm
docker build -t docker-php-fpm-image .
Connecting nginx and php-fpm
Now that we have containers for nginx and php-fpm, we need to connect them. To do so, we have to make sure that both containers are in the same network and can talk to each other [which is a common problem]. Docker provides so called user defined bridge networks allowing automatic service discovery. That basically means, that our nginx container can use the name of the php-fpm container to connect to it. Otherwise we would have to figure out the containers IP address in the default network every time we start the containers.
docker network ls
reveals a list of the current networks
[email protected] MINGW64 /
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
7019b0b37ba7 bridge bridge local
3820ad97cc92 host host local
03fecefbe8c9 none null loca
Now let's add a new one called web-network
for our web stack via
docker network create --driver bridge web-network
[email protected] MINGW64 /
$ docker network create --driver bridge web-network
20966495e04e9f9df9fd64fb6035a9e9bc3aa6d83186dcd23454e085a0d97648
[email protected] MINGW64 /
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
7019b0b37ba7 bridge bridge local
3820ad97cc92 host host local
03fecefbe8c9 none null local
20966495e04e web-network bridge local
Start the nginx container and connect it to the new network via
docker start docker-nginx
docker network connect web-network docker-nginx
Finally, we need to mount the local code folder app\
we mounted to the nginx container at /var/www
also in the php-fpm container in the same location:
docker run -di --name docker-php-fpm -v "C:\codebase\docker-php\app":/var/www --network web-network docker-php-fpm-image
Note that we specified the network in the run command via the --network
option. We can verify that both containers are connected to the web-network
by running
docker network inspect web-network
[email protected] MINGW64 /c/codebase/docker-php/php-fpm
$ docker network inspect web-network
[
{
"Name": "web-network",
"Id": "20966495e04e9f9df9fd64fb6035a9e9bc3aa6d83186dcd23454e085a0d97648",
"Created": "2018-05-30T06:39:44.3107066Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "172.18.0.0/16",
"Gateway": "172.18.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"3358e813423165880d59c8ebc2cb4c563ee8ad1d401595f8bfcf763ff5db8f4a": {
"Name": "docker-php-fpm",
"EndpointID": "d2f1d6285a0932817e1fb8839bef3a6d178f5306a2116307dba200038ea2a3a3",
"MacAddress": "02:42:ac:12:00:03",
"IPv4Address": "172.18.0.3/16",
"IPv6Address": ""
},
"eaa5c05942788985e90a80fa000723286e9b4e7179d0f6f431c0f5109e012764": {
"Name": "docker-nginx",
"EndpointID": "274fa9a6868aff656078a72e19c05fb87e4e86b83aaf12be9b943890140a421d",
"MacAddress": "02:42:ac:12:00:02",
"IPv4Address": "172.18.0.2/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
The "Containers" key reveals that the docker-php-fpm
container has the IP address 172.18.0.3 and the docker-nginx
container is reachable via 172.18.0.2. But can we actually connect from nginx to php-fpm? Let's find out:
Log into the nginx container
winpty docker exec -ti docker-nginx bash
and ping the IP
ping 172.18.0.3 -c 2
[email protected] MINGW64 /c/codebase/docker-php/php-fpm
$ winpty docker exec -ti docker-nginx bash
[email protected]:/# ping 172.18.0.3 -c 2
bash: ping: command not found
.. well, after we make the command available by installing iputils-ping
:
apt-get update && apt-get install iputils-ping -y
ping 172.18.0.3 -c 2
[email protected]:/# apt-get update && apt-get install iputils-ping -y
[email protected]:/# ping 172.18.0.3 -c 2
PING 172.18.0.3 [172.18.0.3] 56[84] bytes of data.
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.142 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.162 ms
--- 172.18.0.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1071ms
rtt min/avg/max/mdev = 0.142/0.152/0.162/0.010 ms
We can ping the container - that's good. But we were also promised we could reach the container by its name docker-php-fpm
:
ping docker-php-fpm -c 2
[email protected]:/# ping docker-php-fpm -c 2
PING docker-php-fpm [172.18.0.3] 56[84] bytes of data.
64 bytes from docker-php-fpm.web-network [172.18.0.3]: icmp_seq=1 ttl=64 time=0.080 ms
64 bytes from docker-php-fpm.web-network [172.18.0.3]: icmp_seq=2 ttl=64 time=0.131 ms
--- docker-php-fpm ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1045ms
rtt min/avg/max/mdev = 0.080/0.105/0.131/0.027 ms
And we can - awesome! Now we need to tell nginx to pass all PHP related requests to
php-fpm by changing the nginx\conf.d\site.conf
file on our windows host to
server {
listen 80;
server_name localhost;
root /var/www;
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass docker-php-fpm:9000;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Note the fastcgi_pass docker-php-fpm:9000;
line that tells nginx how to reach our php-fpm service. Because we mounted the nginx\conf.d
folder, we just need to reload nginx:
nginx -s reload
and open //127.0.0.1:8080/hello-world.php on a browser on your host machine.
Btw. there's also a good tutorial on geekyplatypus.com on how to Dockerise your PHP application with Nginx and PHP7-FPM. But since it's using docker-compose you might want to read the next chapter first :]
Putting it all together: Meet docker-compose
Lets sum up what we have do now to get everything up and running: 1. start php-cli 2. start nginx 3. start php-fpm
docker run -di --name docker-php -v "C:\codebase\docker-php\app":/var/www --network web-network docker-php-image
docker run -di --name docker-nginx -p 8080:80 -v "C:\codebase\docker-php\nginx\conf.d":/etc/nginx/conf.d/ -v "C:\codebase\docker-php\app":/var/www --network web-network docker-nginx-image
docker run -di --name docker-php-fpm -v "C:\codebase\docker-php\app":/var/www --network web-network docker-php-fpm-image
Hm. That's alright I guess... but it also feels like "a lot". Wouldn't it be much better to have everything neatly defined in one place? I bet so! Let me introduce you to docker-compose
Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services. Then, with a single command, you create and start all the services from your configuration.
Lets do this step by step, starting with the php-cli container. Create the file C:\codebase\docker-php\docker-compose.yml
:
# tell docker what version of the docker-compose.yml we're using
version: '3'
# define the network
networks:
web-network:
# start the services section
services:
# define the name of our service
# corresponds to the "--name" parameter
docker-php-cli:
# define the directory where the build should happened,
# i.e. where the Dockerfile of the service is located
# all paths are relative to the location of docker-compose.yml
build:
context: ./php-cli
# reserve a tty - otherwise the container shuts down immediately
# corresponds to the "-i" flag
tty: true
# mount the app directory of the host to /var/www in the container
# corresponds to the "-v" option
volumes:
- ./app:/var/www
# connect to the network
# corresponds to the "--network" option
networks:
- web-network
Before we get started, we're gonna clean up the old containers:
docker rm -f $[docker ps -aq]
To test the docker-compose.yml we need to run docker-compose up -d
from C:\codebase\docker-php
cd "C:\codebase\docker-php"
docker-compose up -d
[email protected] MINGW64 /c/codebase/docker-php
$ docker-compose up -d
Creating network "docker-php_web-network" with the default driver
Building docker-php-cli
Step 1/2 : FROM php:7.0-cli
---> da771ba4e565
Step 2/2 : RUN pecl install xdebug-2.6.0 && docker-php-ext-enable xdebug
---> Using cache
---> 12be27256b12
Successfully built 12be27256b12
Successfully tagged docker-php_docker-php-cli:latest
Image for service docker-php-cli was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating docker-php_docker-php-cli_1 ... done
Note that the image is build from scratch when we run docker-compose up
for the first time. A docker ps -a
shows that the container is running fine, we can log in and execute source code from the host machine.
[email protected] MINGW64 /c/codebase/docker-php
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
adf794f27315 docker-php_docker-php-cli "docker-php-entrypoi…" 3 minutes ago Up 2 minutes docker-php_docker-php-cli_1
Logging in
winpty docker exec -it docker-php_docker-php-cli_1 bash
and running
php /var/www/hello-world.php
works as before
[email protected]:/# php /var/www/hello-world.php
Hello World [php]
Now log out of the container and run
docker-compose down
to shut the container down again:
[email protected] MINGW64 /c/codebase/docker-php
$ docker-compose down
Stopping docker-php_docker-php-cli_1 ... done
Removing docker-php_docker-php-cli_1 ... done
Removing network docker-php_web-network
Add the remaining services to the docker-compose.yml
file:
# tell docker what version of the docker-compose.yml we're using
version: '3'
# define the network
networks:
web-network:
# start the services section
services:
# define the name of our service
# corresponds to the "--name" parameter
docker-php-cli:
# define the directory where the build should happened,
# i.e. where the Dockerfile of the service is located
# all paths are relative to the location of docker-compose.yml
build:
context: ./php-cli
# reserve a tty - otherwise the container shuts down immediately
# corresponds to the "-i" flag
tty: true
# mount the app directory of the host to /var/www in the container
# corresponds to the "-v" option
volumes:
- ./app:/var/www
# connect to the network
# corresponds to the "--network" option
networks:
- web-network
docker-nginx:
build:
context: ./nginx
# defines the port mapping
# corresponds to the "-p" flag
ports:
- "8080:80"
tty: true
volumes:
- ./app:/var/www
- ./nginx/conf.d:/etc/nginx/conf.d
networks:
- web-network
docker-php-fpm:
build:
context: ./php-fpm
tty: true
volumes:
- ./app:/var/www
networks:
- web-network
And up again...
docker-compose up -d
[email protected] MINGW64 /c/codebase/docker-php
$ docker-compose up -d
Building docker-nginx
Step 1/1 : FROM nginx:latest
---> ae513a47849c
Successfully built ae513a47849c
Successfully tagged docker-php_docker-nginx:latest
Image for service docker-nginx was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Building docker-php-fpm
Step 1/2 : FROM php:7.0-fpm
---> a637000da5a3
Step 2/2 : RUN pecl install xdebug-2.6.0 && docker-php-ext-enable xdebug
---> Running in 4ec27516df54
downloading xdebug-2.6.0.tgz ...
Starting to download xdebug-2.6.0.tgz [283,644 bytes]
[...]
---> 120c8472b4f3
Successfully built 120c8472b4f3
Successfully tagged docker-php_docker-php-fpm:latest
Image for service docker-php-fpm was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating docker-php_docker-nginx_1 ... done
Creating docker-php_docker-php-cli_1 ... done
Creating docker-php_docker-php-fpm_1 ... done
Only nginx and php-fpm needed to be built because the php-cli one already existed. Lets check if we can still open //127.0.0.1:8080/hello-world.php in a browser on the host machine:
Yes we can! So instead of needing to run 3 different command with a bunch of parameters we're now down to docker-compose up -d
. Looks like an improvement to me ;]
The tl;dr
The whole article is a lot to take in and it is most likely not the most efficient approach when you "just want to get started". So in this section we'll boil it down to only the necessary steps without in depth explanations.
- Download Docker for Windows
- Install
Docker
- activate Hyper-V [Virtual Box will stop working]
- enable Disk Sharing in the settings
- Set up the following folder structure ```` C:\codebase\docker-php
- nginx\
- conf.d\
- site.conf
- Dockerfile
- php-cli\
- Dockerfile
- php-fpm\
- Dockerfile
- app\
- index.html
- hello-world.html
- docker-compose.yml ````
- or simply
git clone [email protected]:paslandau/docker-php-tutorial.git docker-php && git checkout part_1_setting-up-php-php-fpm-and-nginx-for-local-development-on-docker
- nginx\
- Open a shell at
C:\codebase\docker-php
- run
docker-compose up -d
- check in browser via
- 127.0.0.1:8080
- 127.0.0.1:8080/hello-world.php
- run
docker-compose down
Your application code lives in the app\
folder and changes are automatically available to the containers. This setup denotes the end of the first tutorial. In the next part we will learn how to set up Docker in PHPStorm, especially in combination with xdebug.
Wrapping up
Congratulations, you made it! If some things are not completely clear by now, don't hesitate to leave a comment. Apart from that, you should now have a first idea on what docker is and how you can use it.
If you want to go deeper, please check out the remaining articles of the Docker PHP Tutorial series.
Please subscribe to the RSS feed or via email to get automatic notifications when this next part comes out :]
Wanna stay in touch?
Since you ended up on this blog, chances are pretty high that you're into Software Development [probably PHP, Laravel, Docker or Google Big Query] and I'm a big fan of feedback and networking.
So - if you'd like to stay in touch, feel free to shoot me an email with a couple of words about yourself and/or connect with me on LinkedIn or Twitter or simply subscribe to my RSS feed or go the crazy route and subscribe via mail and don't forget to leave a comment :]