So you have your php application running smoothly on your local machine. That’s really good! But preparing it for deployment can be tricky. Here’s some things I learned by doing it:

What we’ll do is to create a docker image with our application, so we can just docker run it.

If you are not familiarised with Docker, I recommend learning some before continuing since nowadays it is a must-know for developers.

Setting everything up

The application will run in only one container with PHP-FPM, NGINX and Supervisor.

I’ve seen lots of tutorials and articles about running PHP-FPM and NGINX in different containers. This is not good for deployment because both PHP-FPM and NGINX need to access the code and the only way to achieve this is to share the source code through a volume (named or local), which is not very good: we want our code to be immutable, encapsulated within the docker image.

Let’s create some files for our build process:

  • Project root
    • build/
      • site.conf
      • supervisor.conf
    • Dockerfile
    • docker-compose.yml
    • .dockerignore

The Dockerfile will be in our root-level dir because we want to copy our source code to the image, and we cannot copy anything outside the build context.

# Dockerfile
FROM phusion/baseimage
ENV DEBIAN_FRONTEND noninteractive

# Install PHP
RUN add-apt-repository -y ppa:ondrej/php && apt-get update
RUN apt-get install -y \
# Only install the php extensions you need.
php7.1-fpm \
php7.1-mcrypt \
php7.1-intl \
php7.1-mbstring \
php7.1-xml \
php7.1-dom \
php7.1-zip \

# Create socket directory
RUN mkdir -p /var/run/php

# Don't clear env variables
# This is very important since it will allow us to read environment variables from the container.
RUN sed -e 's/;clear_env = no/clear_env = no/' -i /etc/php/7.1/fpm/pool.d/www.conf

# Install nginx
RUN apt-get install -y nginx

COPY ./build/site.conf /etc/nginx/conf.d/default.conf

# Install supervisor
RUN apt-get install -y supervisor

ADD ./build/supervisor.conf /etc/supervisor/conf.d/my-app.conf

# Copy application files
COPY . /var/www/html

# Install and run composer
RUN apt-get install -y wget git zip
RUN cd /var/www/html && chmod +x ./build/ && ./build/
RUN cd /var/www/html && php composer.phar install --no-scripts && rm composer.phar

# Allow php to write cache and logs. Note that the cache and data are outside the application directory because we don't want this data to be immutable.
# You can add the directories you want to store data to persist through deployments.
RUN mkdir -p /data/cache
RUN mkdir -p /data/logs
# Remember to allow php-fpm to write on those.
RUN chown -R www-data:www-data /data/cache
RUN chown -R www-data:www-data /data/logs
RUN chown -R www-data:www-data /var/www/html

WORKDIR /var/www/html

# Run supervisor
CMD ["supervisord", "-n"]

This will be the configuration for our NGINX

# build/site.conf
server {
    listen 7102;
    server_name ~.;
    root /var/www/html/public;

    location / {
        try_files $uri /index.php$is_args$args;

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;

    location ~ \.php$ {
        return 404;

The supervisor configuration file

logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/ ; (supervisord pidfile;default
childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)

command=/usr/sbin/nginx -g 'daemon off;'

command=/usr/sbin/php-fpm7.1 -F

This composer installation file is taken from Composer’s documentation.


# build/
php -r "copy('', 'composer-setup.php');"
ACTUAL_SIGNATURE="$(php -r "echo hash_file('SHA384', 'composer-setup.php');")"

    >&2 echo 'ERROR: Invalid installer signature'
    rm composer-setup.php
    exit 1

php composer-setup.php --quiet
rm composer-setup.php
exit $RESULT

Remember to add volumes for other data you want to keep.

# docker-compose.yml
version: '3'
    container_name: my_php_app
    build: .
      - "80:80"
      - cache:/data/cache
      - logs:/data/logs

Here we will set some file we don’t want to COPY to our image:

# Example for a Symfony 4 app
# We will re-download the third-party libraries in the build, so we won't copy this folder

The environment

It is important that your application knows the environment it’s running on by reading an environment variable. We’ll call it APP_ENV. You should be able to read it in your app with


This variable should be available inside the docker container. The idea is to build only one image that you can deploy in different environments.

docker build -t my-image .

That should get you the final image. To run it, you can

docker run -v "cache:/data/cache" -v "logs:/data/logs" -e "APP_ENV=dev" -p 80:80 my-image:latest

Here, we say: “Ok docker, run the image i just built, create the volumes to persist data outside the image, expose the port 80 and set environment to dev”.

You can read more about volumes here.

Here’s a nice article that helped me a lot:

The idea is to keep it fairly simple: don’t get tangled with ENV and ARG keywords. If you get to that, take a walk and start from scratch: it will come around :)

Edit (2018-05-10)

Added this article on how to install New Relic within this very Dockerfile.