DEVELOPMENT by Pedro Resende

How to setup Docker for a Symfony Project with Nginx

Docker is a fast grower in open source software development. Follow our simple steps to easily set up a Docker environment in your project.

Post by Pedro Resende on the 21 of January of 2018 at 05:52

What exactly is Docker?

Let's start at the beginning and explain a bit what a docker is. In simple words, a docker is a standardised unit of software that is independent of its host. The Docker open platform makes it easier for developers to create, deploy and run applications within containers instead of virtual machines, adding a layer of abstraction. A container is an abstraction at the application layer that packages code and dependencies together, therefore eliminating the overhead of a full copy of an operating system.

Before getting started to build our environment, you need to install Docker and Docker image.

The structure

Let’s define the structure of our Docker environment:

We have 4 folders and the first one – build – is responsible for storing the information about each of the dockers that will live in our container. The second one – logs - will be used to store the log file from NGINX and PHP. The mysql folder will store the database in a persistent way in the host and finally the symfony folder will host the Symfony installation.

-> build
-> logs
-> mysql
-> symfon

Inside the build folder, let’s start by creating two folders, one for NGINX and another PHP:

-> nginx

-> php

NGINX docker

Inside NGINX folder, let’s create a file called Dockerfile, which will host the necessary packages:

FROM debian:stretch

RUN apt-get update && apt-get install -y nginx vim nano
COPY nginx.conf /etc/nginx/nginx.conf
ADD sites-enabled /etc/nginx/sites-enabled
RUN rm /etc/nginx/sites-enabled/default

WORKDIR /var/www/html/symfony

EXPOSE 80
EXPOSE 443

CMD ["nginx"]

Explaining a bit, each of the base commands declared inside our Docker file:

  • FROM is to define in which image this docker will be based on, in our case I’ll use the latest build from Ubuntu,
  • RUN is used to execute command inside the docker,
  • COPY will copy a specific file from the host to the docker,
  • ADD will copy a specific folder from the host to the docker,
  • WORKDIR is used to define the base folder inside the docker,
  • EXPOSE defines the ports that will be available to connect from the host to the guest,
  • CMD is responsible for executing a command/command.

In this docker file, we’ll install NGINX packages on Ubuntu images and will copy the nginx.conf file from the host and add the content of the folder sites-enabled and finally execute NGINX.

Inside the nginx.conf file we have the following:

user www-data;
pid /run/nginx.pid;

worker_processes  1;
daemon off;

events {
        worker_connections 768;
        # multi_accept on;
}

http {
        client_max_body_size 20M;

        ##
        # Basic Settings
        ##
        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;
        # server_tokens off;
        # server_names_hash_bucket_size 64;
        # server_name_in_redirect off;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##
        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##
        gzip on;
        gzip_disable "msie6";
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        gzip_buffers 16 8k;
        gzip_http_version 1.1;
        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

        ##
        # Virtual Host Configs
        ##
        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;
}

Typically this is the base of nginx.conf file, with the exception of running on a single process, due to worker_process and running on the foreground with daemon off;

Inside the sites-enabled folder, we’ll have a unique file, called default_symfony with the following:

server {
    server_name _;

    root /var/www/html/symfony/web;

    location / {
        # try to serve file directly, fallback to app.php
        try_files $uri /app_dev.php$is_args$args;
    }

    # DEV
    # This rule should only be placed on your development environment
    # In production, don't include this and don't deploy app_dev.php or config.php
    location ~ ^/(app_dev|config)\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
    }

    # PROD
    location ~ ^/app\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        # Prevents URIs that include the front controller. This will 404:
        # http://domain.tld/app.php/some-path
        # Remove the internal directive to allow URIs like this
        internal;
    }

    # return 404 for all other php files not matching the front controller
    # this prevents access to other php files you don't want to be accessible.
    location ~ \.php$ {
        return 404;
    }

    error_log /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
}

This file is the default Symfony configuration for NGINX, with the exception of fastcgi_pass php:9000. This will tell NGINX to use the PHP from the host PHP.

PHP docker

Inside PHP folder, let’s create a file called Dockerfile, which will host the necessary packages:

FROM ubuntu:17.10

RUN apt-get update && apt-get install -my \
  curl \
  wget \
  php-curl \
  php-fpm \
  php-gd \
  php-xsl \
  php-mysqlnd \
  php-mcrypt \
  php-cli \
  php-intl \
  php-bz2 \
  php-zip \
  php-mbstring \
  git \
  zip \
  php-apcu \
  php-redis \
  php-opcache

RUN mkdir /run/php

ADD conf/www.conf /etc/php/7.1/fpm/pool.d/www.conf
ADD conf/php-fpm.conf /etc/php/7.1/fpm/php-fpm.conf

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
RUN mv composer.phar /usr/local/bin/composer
RUN chmod +x /usr/local/bin/composer
RUN ln -snf /usr/share/zoneinfo/Europe/Lisbon /etc/localtime

WORKDIR /var/www/html/symfony

EXPOSE 9000

CMD ["php-fpm7.1"]

In this docker file, we’ll install PHP packages on Ubuntu image with composer and will copy the www.conf and php-fpm.conf files from the host and add the content of the guest folders.

Inside www.conf add

[www]
user = www-data
group = www-data
listen = 0.0.0.0:9000
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
chdir = /

And php-fpm.conf file

[global]
pid = /run/php/php7.1-fpm.pid
error_log = /var/log/php7.1-fpm.log
daemonize = no
include=/etc/php/7.1/fpm/pool.d/*.conf

And the base configuration for this Dockerfile, that will host PHP is done.

Docker-compose (The Connector)

Finally, the docker-compose.yml file will be responsible for connecting all the dockers with a local network, inside that file we’ll have:

version: '3'

services:
  php:
    build: build/php
    expose:
      - '9000'
    depends_on:
      - db
    volumes:
      - ./:/var/www/html/symfony:cached
      - ./logs:/var/log
  web:
    build: build/nginx
    restart: always
    ports:
        - '81:80'
    depends_on:
        - php
        - db
    volumes:
        - ./:/var/www/html/symfony:cached
        - ./logs:/var/log/nginx
  db:
    image: mysql/mysql-server:latest
    environment:
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_ROOT_HOST=${MYSQL_ROOT_HOST}
    ports:
      - '3307:3306'
    volumes:
      - ./mysql:/var/lib/mysql

We’ll have 3 dockers, one for php the will expose the port 9000 to be consumed by nginx and deal with the php files. Another for nginx, called web that will expose the por 81 to the host to be able to access the docker container and the last one db, which will run mysql.

To start the environment you’ll need to run the command:

$ docker-compose build

That will build the containers.

Remember to add your project to the Symfony folder or simply run:

$ docker-compose up

To start the docker network.

Now, all you have to do is open your browser on the address http://localhost:81.

As you can see, it’s not that hard! I hope this guide is useful and makes your developer days easier.