Docker+PHP+Nginx: Part 2

The Docker+PHP+Nginx: Part 1 shows a way to set up Docker, PHP and Nginx, and create files and write log(writing permission in docker is a complex problem) in the php container.

Now part 2 will explain how it solves the important problems:

  • php-fpm's writing permission in docker container(on Linux, not on macOS)
  • fastcgi_param: 'SCRIPT_FILENAME' in nginx.conf

This tutorial's code is put at GitHub

The code works with:

  • macOS
  • Linux

Contents

Dockerfile and the writing permission

Dockerfile

FROM php:7.4-fpm

ARG UID
ARG GID
ARG USER_NAME
ARG GROUP_NAME

ENV UID=${UID}
ENV USER_NAME=${USER_NAME}

ENV GID=${GID}
ENV GROUP_NAME=${GROUP_NAME}

WORKDIR /var/www

RUN addgroup -gid ${GID} --system ${GROUP_NAME}
RUN adduser --system --ingroup ${GROUP_NAME} -shell /bin/sh --uid ${UID} --disabled-password ${USER_NAME}

ADD ./src/  /var/www

RUN chown -R ${USER_NAME}:${GROUP_NAME} /var/www

USER ${USER_NAME}

Key Lines:

  • RUN addgroup -gid ${GID} --system ${GROUP_NAME}:
    add a new system group with a name(stored in parameter "${UID}") and gid(stored in parameter ${GID}), and you can control them through the ".env.dev"
  • RUN adduser --system --ingroup ${GROUP_NAME} -shell /bin/sh --uid ${UID} --disabled-password ${USER_NAME}: add a new user to the group, and disable password for the user
  • ADD ./src/ /var/www: load source files to the work directory "/var/www"
  • RUN chown -R ${USER_NAME}:${GROUP_NAME} /var/www: change the permissions of files in "/var/www" to the new user and group
  • USER ${USER_NAME}: use the new user to run php-fpm process in container

The writing permission problem: permission denied

Writing permission in docker container is tricky, mainly due to the fact the directories and files we mount in docker containers have the same permission as on the host, but the process in the container to run the application belongs to a different user(usually is "root"). The user owns the files and the user wants to write the files are different causes the permission problem.

Be careful, the permission problem exists on Linux, not on macOS, because Docker for Mac actually runs a Linux VM, and inside that VM it mounts host filesystems into the container as a network volume[1]

Let me give you a detailed example(on Linux):

The file permission error example code(you can download and execute) is what I specially created for wrong permission illustration. I don't create a new user, group, and also not name the user to run php-fpm in Dockerfile, let's see what will happen.

I execute the code with command sudo docker-compose up nginx -d --no-deps --build app to run the containers, then I open "localhost:8000", it failed, the error msg is "error_log(../log/error.log): failed to open stream: Permission denied...":
docker-php-nginx-8

Let's explore the reason.

The permission pass from host to container

As the following image shows, the files on host belong to user "ec2-user" and group "ec2-user":
docker-php-nginx-5

As the following image shows, the uid and gid of ec2-user are both 1000:
docker-php-nginx-4

I enter the running app container with command sudo docker exec -it app bash, and use `ls -l /var/www" to check files' permission, they belong to user "1000" and group "1000"
docker-php-nginx-6

The user 1000 and group 1000 in container are exactly the ec2-user on host. The container shows number not name, because the kernel distinguishes users and groups by two numbers: the user ID (UID) and the group ID (GID)[2].

So the permissiones on host pass along to containers.

And if you look closely look at the webpage of "localhost:8000", it says "Current user: www-data", so the user now who is trying to write log is "www-data", but the file ownership belong to "ec2-user", that is why it fails.

"www-data" is php-fpm's default user, you can find this info inside php container at "/etc/nginx/conf.d/default.conf", around lines 23-24, it sets the default user:
docker-php-nginx-9

Solution: replicate permission to containers and change the user who writes(run php-fpm)

As showed in dockerfile key lines, the solution is to create a new user with the same permission as "ec2-user", and give it the files' permission, then change the user who is going to run php-fpm process.

nginx.conf and the fastcgi_param: 'SCRIPT_FILENAME'

server {
    listen 80;
    index index.php index.html;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    root /var/www/public;

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        include fastcgi_params;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip_static on;
    }
}

For nginx configuring, you need to be careful with one line in "nginx.conf": fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;, the "SCRIPT_FILENAME" is the SCRIPT_FILENAME parameter is required as it is passed to PHP FPM to determine the script name[3]. So the "SCRIPT_FILENAME" actually points to file path in php container(although the "nginx.conf" file is located in nginx container).

$document_root$fastcgi_script_name is the default configure for "SCRIPT_FILENAME", $document_root is the "root" in "nginx.conf", in this example, it's "/var/www/public". If the project file is not located at "/var/www/public" in php container, you need to change the "$document_root" to the real directory path in php container.

References


  1. Docker and the Host Filesystem Owner Matching Problem ↩︎

  2. Docker and the Host Filesystem Owner Matching Problem ↩︎

  3. PHP FastCGI Example ↩︎