A handy local dev tip that works great with Google’s PHP Image

Daniel Holmes
Google Cloud - Community
4 min readJul 24, 2019

First of all, let me highlight something… If you have an issue doing local development with docker volumes because of file permissions this might actually help you! So I’ll keep the instructions as generic as possible so you can do this with any technology mix.

Setting up the Docker container

In my case this is a simple matter of creating the following Dockerfile:

FROM gcr.io/google-appengine/php:latest

Note: It’s not good practice to use the :latest tag with any docker image because a new version of the container may be released which breaks future builds or deployments. You should use a pinned label if you plan on deploying your Docker container into production.

What this image does is copy anything in the same directory as the Dockerfile into the container’s /app directory. It then checks the composer.json file in /app to see what version of php is specified if any, and then spins up a php-fpm instance running under supervisord with nginx as the reverse proxy.

In the case of your particular setup you might simply have a container that spins up apache2 with mod_php. Whatever your setup, you’ll need to know what process will be running php whether it is apache2 or php-fpm for the next step.

Finding out more information about the docker image

Now that we have our Dockerfile we want to run it, and then hop inside the container to see what’s going on:

$ docker build -t myapp .
...
$ docker run -v "/${PWD#*/}":/app myapp
2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2413fb3709b0 myapp "docker-entrypoint.s…" 5 seconds ago Up 5 seconds kicking_tyrant
$ docker exec -it kicking_tyrant bash
root@2413fb3709b0:/app# top
top - 10:53:54 up 5:21, 0 users, load average: 0.05, 0.02, 0.00
Tasks: 9 total, 1 running, 8 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.7 us, 0.7 sy, 0.0 ni, 98.7 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 4037124 total, 206692 free, 1885512 used, 1944920 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 1807612 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1 root 20 0 55736 17820 6820 S 0.0 0.4 0:00.19 supervisord
23 root 20 0 366168 22488 18208 S 0.0 0.6 0:00.06 php-fpm
24 root 20 0 172780 14548 11560 S 0.0 0.4 0:00.03 nginx
25 www-data 20 0 174168 6660 2356 S 0.0 0.2 0:00.00 nginx
26 www-data 20 0 174168 6660 2356 S 0.0 0.2 0:00.00 nginx
27 www-data 20 0 366168 7416 3132 S 0.0 0.2 0:00.00 php-fpm
28 www-data 20 0 366168 7416 3132 S 0.0 0.2 0:00.00 php-fpm
29 root 20 0 18240 3244 2796 S 0.0 0.1 0:00.02 bash
38 root 20 0 36628 3040 2620 R 0.0 0.1 0:00.00 top

The important part to take note of here, is that php-fpm is running as www-data as is nginx. If this were a different setup you might see php-fpm + apache or just apache2 (or httpd2).

Now that we know what user it’s running at we need to determine what the user id of www-data is and lets also look at what our user id is on the host machine:

root@2413fb3709b0:/app# id -u www-data
33
root@2413fb3709b0:/app# exit
$ id -u
1000

So now we know the default user id the container uses for the www-data user. Now we can go back to the Dockerfile and modify it to include some extra goodness in preparation for our hack.

Adding goodness to our Dockerfile

Lets add two simple statements to our Dockerfile so that it looks like this:

FROM gcr.io/google-appengine/php:latestARG WWW_DATA_UID=33
RUN usermod -u $WWW_DATA_UID www-data && groupmod -g $WWW_DATA_UID www-data

What this does, is looks for a build argument called WWW_DATA_UID during the docker image build process and then changes the user id and group id of www-data to the value of WWW_DATA_UID. If we don’t specify anything it uses the default that we found the image came with (33) so it’s production-safe (tm).

Here’s where the magic happens

The final piece of our puzzle is to add some extra goodness when we’re building our dockerfile locally. Lets use the above run example but modified to pass in the WWW_DATA_UID build argument:

$ docker build -t myapp --build-arg WWW_DATA_UID=$(id -u) .
...
$ docker run -v "/${PWD#*/}":/app myapp
2413fb3709b05939f04cf2e92f7d0897fc2596f9ad0b8a9ea855c7bfebaae892

Voila! If we take a look inside our container now we’ll see that the user id of www-data has changed so that the userid of our files outside of the container match the user-id of the files inside the container (let’s pretend the container still has the same name the second time through):

$ docker exec -it kicking_tyrant bash
root@2413fb3709b0:/app# id -u www-data
1000

Bonus Points!

That’s all there is to it, however if you are using the exact same image as I’m using in my example, take a look at my previous article which will help you avoid that pesky (you can’t write to this file) error that happens because the docker image tries to lock down all the files.

Comment below and let me know how this worked for you!

--

--