How to share files from PHP Docker image to Nginx
21 June 2023
If we are given an image with packed PHP application and it also contains resources like images, plain HTML files, we would like to serve them directly, without involvement of FPM. On a single instance, whether it is a container or a VM, it is easy - we just point both of them into the same directory. But what if we have two separate containers and we want to avoid building both with all the files included and keep the application files as part of the versioned image?
Repository for this post available here: https://github.com/ppabis/DockerShareVolume
To clarify this example, we will have the following Dockerfile
for PHP image.
FROM php:7.4-fpm-alpine
COPY --chown=www-data:www-data index.php /srv/
COPY --chown=www-data:www-data assets /srv/
WORKDIR /srv
This image definition is simple, we just copy index.php
and some assets
to
the container. Let's write some test script and add some images to the assets
directory. This simple script will just scan the files in the assets
and
show them as images in HTML.
<!DOCTYPE html>
<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8" />
</HEAD>
<BODY>
<?php
$files = scandir('assets');
foreach ($files as $file) {
if (is_file('assets/' . $file)) {
echo '<h3>' . $file . '</h3>';
echo '<img src="assets/' . $file . '" />';
}
}
?>
</BODY>
</HTML>
Create assets
directory, put some images there and build this container. Run
with a plain Nginx configuration that just forwards to CGI.
server {
listen 80;
server_name _;
root /srv;
location ~ .* {
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Using Docker compose it will look like this.
version: '3.3'
networks:
phpnginx:
driver: bridge
services:
php:
build: .
networks:
- phpnginx
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
ports:
- 8080:80
networks:
- phpnginx
depends_on:
- php
So run docker-compose up -d
and try localhost:8080
. After trying to load the
page, we will see the list of files generated but the <img>
will be broken.
In the PHP container logs we can see:
2023-06-16 16:07:38 NOTICE: Access to the script
'/srv/assets/components-of-kubernetes.svg' has been denied (see
security.limit_extensions)
Let us try changing the value in PHP configuration. Add this line to Dockerfile
RUN sed -i 's/;security\.limit_extensions.*/security.limit_extensions = .php .png/g' /usr/local/etc/php-fpm.d/www.conf
Run docker-compose down
, rebuild the image (docker-compose build
) and try
again. Now we will see that one .png
image is showing up but the other is not.
Warning: Unexpected character in input: '' (ASCII=127) state=0 in
/srv/assets/muilti-datacenter.png on line 131
It seems that this method is not promising. It would also be considered totally insecure if we were planning to let people upload their own images. We should use Nginx to serve static files. It will also be more performant then sending back and forth between PHP and Nginx containers. However, if some static files are packed inside the container we need to create a shared volume that will also copy the files from the PHP image first. In Docker compose we can do it like this.
...
volumes:
app:
services:
php:
build: .
volumes: # Important part
- type: volume
source: app
target: /srv
volume:
nocopy: false
...
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- app:/srv:ro
...
First we have to declare a Docker volume, without any host mount point. Next we
add the volume to the PHP container. The nocopy
set to false
option will
copy the files from PHP container into the volume. Then we mount the same volume
in Nginx container under the same path. The ro
option will make it read-only.
And in nginx.conf
we would need to add a new location for static files. Files
ending with .php
will be forwarded to FPM and the rest will be served by
Nginx.
location ~ .*\.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location / {
try_files $uri $uri/ =404;
}
This time the images will be loaded correctly. We can also see that the files
were served by Nginx and FPM only served index.php
.
# FPM
2023-06-20 21:38:48 192.168.32.3 - 20/Jun/2023:19:38:48 +0000 "GET /index.php" 200
# Nginx
2023-06-20 21:39:32 192.168.32.1 - - [20/Jun/2023:19:39:32 +0000] "GET /index.php HTTP/1.1" 200 353 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15" "-"
2023-06-20 21:39:32 192.168.32.1 - - [20/Jun/2023:19:39:32 +0000] "GET /assets/components-of-kubernetes.png HTTP/1.1" 200 61595 "http://localhost:8080/index.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15" "-"
2023-06-20 21:39:32 192.168.32.1 - - [20/Jun/2023:19:39:32 +0000] "GET /assets/multi-datacenter.png HTTP/1.1" 200 35680 "http://localhost:8080/index.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15" "-"
2023-06-20 21:39:32 192.168.32.1 - - [20/Jun/2023:19:39:32 +0000] "GET /assets/docker.png HTTP/1.1" 200 4570 "http://localhost:8080/index.php" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Safari/605.1.15" "-"
That way we can avoid splitting the project into two distributions of backend
and frontend but pack the release into a single container and use Nginx to serve
what should be static. When changing the image, remember to also remove the
volume created by Docker Compose docker-compose down -v
, otherwise the changes
in PHP image will not be copied to the volume.