This example makes use of Nginx, MySQL and PHP-FPM docker containers (debian:stretch-slim) to run Symfony application in development DEV environment and contains only the bare minimum modules so you can adapt it to your needs. All you have to do is, copy docker folder below into your symfony application root then run docker-compose up -d command in symfony/docker folder. That's all!


Timezone


Although it all depends on your own requirements, the safest timezone for servers is the UTC rather than DST (Daylight Savings) dependent ones. In example below I am setting it to Europe/London (GMT - this is a DST) but you can either remove all those lines or just replace with UTC.


Structure


$ tree -a
.
└── dev
   ├── docker-compose.yml
   ├── .env
   ├── mysql
   │   ├── Dockerfile
   │   └── mysqld.cnf
   ├── nginx
   │   ├── app.conf
   │   ├── Dockerfile
   │   └── nginx.conf
   └── php
   ├── Dockerfile
   ├── php.ini
   └── www.conf

Files


.env


COMPOSE_PROJECT_NAME=inanzzz
REPOSITORY_NAME=dsa
IMAGE_TAG=latest

MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=dsa

docker-compose.yml


version: '3'

services:

dsa_mysql:
build:
context: ./mysql
image: '${COMPOSE_PROJECT_NAME}/${REPOSITORY_NAME}_mysql:${IMAGE_TAG}'
container_name: '${REPOSITORY_NAME}_mysql'
hostname: '${REPOSITORY_NAME}-mysql'
volumes:
- ./mysql/mysqld.cnf:/etc/mysql/mysql.conf.d/mysqld.cnf:ro
- ../../var/log/docker/mysql:/var/log/mysql:consistent
environment:
MYSQL_DATABASE: '${MYSQL_DATABASE}'
MYSQL_ROOT_PASSWORD: '${MYSQL_ROOT_PASSWORD}'

dsa_php:
build:
context: ./php
image: '${COMPOSE_PROJECT_NAME}/${REPOSITORY_NAME}_php:${IMAGE_TAG}'
container_name: '${REPOSITORY_NAME}_php'
hostname: '${REPOSITORY_NAME}-php'
volumes:
- ../..:/app:consistent
- ./php/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro
- ./php/php.ini:/usr/local/etc/php/conf.d/php.override.ini:ro
working_dir: /app

dsa_nginx:
build:
context: ./nginx
image: '${COMPOSE_PROJECT_NAME}/${REPOSITORY_NAME}_nginx:${IMAGE_TAG}'
container_name: '${REPOSITORY_NAME}_nginx'
hostname: '${REPOSITORY_NAME}-nginx'
ports:
- '8081:80'
volumes:
- ../..:/app:consistent
- ./nginx/app.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ../../var/log/docker/nginx:/var/log/nginx:consistent
depends_on:
- dsa_mysql
- dsa_php

docker/mysql/Dockerfile


# debian:stretch-slim
FROM mysql:5.7.24

# Timezone
RUN ln -snf /usr/share/zoneinfo/Europe/London /etc/localtime \
&& echo Europe/London > /etc/timezone
#

docker/mysql/mysqld.cnf


[mysqld]

pid-file=/var/run/mysqld/mysqld.pid
socket=/var/run/mysqld/mysqld.sock
datadir=/var/lib/mysql
symbolic-links=0

character_set_server=utf8mb4
collation_server=utf8mb4_unicode_ci

explicit_defaults_for_timestamp=1

; LOGS
; General Query Log
general_log_file=/var/log/mysql/general_query.log
general_log=1
; Slow Query Logs
slow_query_log=1
long_query_time=1 #seconds
slow_query_log_file=/var/log/mysql/slow_query.log
log_queries_not_using_indexes=0
; Error Log
log_error=/var/log/mysql/error.log
[mysqld_safe]
log_error=/var/log/mysql/error.log

docker/nginx/Dockerfile


# debian:stretch-slim
FROM nginx:1.15.7

# Timezone
RUN ln -snf /usr/share/zoneinfo/Europe/London /etc/localtime \
&& echo Europe/London > /etc/timezone
#

docker/nginx/app.conf


server {
listen 80 default_server;

server_name localhost;

root /app/public;

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

location ~ ^/index\.php(/|$) {
fastcgi_pass dsa_php:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_hide_header X-Powered-By;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
internal;
}

location ~ \.php$ {
return 404;
}

error_log /var/log/nginx/app_error.log;
access_log /var/log/nginx/app_access.log;
}

docker/nginx/nginx.conf


user nginx;

# 1 worker process per CPU core.
# Check max: $ grep processor /proc/cpuinfo | wc -l
worker_processes 2;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
# Tells worker processes how many people can be served simultaneously.
# worker_process (2) * worker_connections (2048) = 4096
# Check max: $ ulimit -n
worker_connections 2048;

# Connection processing method. The epoll is efficient method used on Linux 2.6+
use epoll;
}

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

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

# Used to reduce 502 and 504 HTTP errors.
fastcgi_buffers 8 16k;
fastcgi_buffer_size 32k;
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;

# The sendfile allows transfer data from a file descriptor to another directly in kernel.
# Combination of sendfile and tcp_nopush ensures that the packets are full before being sent to the client.
# This reduces network overhead and speeds the way files are sent.
# The tcp_nodelay forces the socket to send the data.
sendfile on;
tcp_nopush on;
tcp_nodelay on;

# The client connection can stay open on the server up to given seconds.
keepalive_timeout 65;

# Hides Nginx server version in headers.
server_tokens off;

# Disable content-type sniffing on some browsers.
add_header X-Content-Type-Options nosniff;

# Enables the Cross-site scripting (XSS) filter built into most recent web browsers.
# If user disables it on the browser level, this role re-enables it automatically on serve level.
add_header X-XSS-Protection '1; mode=block';

# Prevent the browser from rendering the page inside a frame/iframe to avoid clickjacking.
add_header X-Frame-Options DENY;

# Enable HSTS to prevent SSL stripping.
add_header Strict-Transport-Security 'max-age=31536000; includeSubdomains; preload';

# Prevent browser sending the referrer header when navigating from HTTPS to HTTP.
add_header 'Referrer-Policy' 'no-referrer-when-downgrade';

# Sets the maximum size of the types hash tables.
types_hash_max_size 2048;

# Compress files on the fly before transmitting.
# Compressed files are then decompressed by the browsers that support it.
gzip on;

include /etc/nginx/conf.d/*.conf;
}

docker/php/Dockerfile


# debian:stretch-slim
FROM php:7.2.13-fpm

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
locales \
git \
zip \
unzip \
&& pecl install xdebug

# PHP extensions
RUN docker-php-ext-install opcache pdo_mysql mysqli
RUN docker-php-ext-enable opcache xdebug
#

# Timezone
RUN ln -snf /usr/share/zoneinfo/Europe/London /etc/localtime \
&& echo Europe/London > /etc/timezone
#

# Language
RUN sed -i 's/# en_GB.UTF-8 UTF-8/en_GB.UTF-8 UTF-8/g' /etc/locale.gen \
&& locale-gen en_GB.UTF-8

ENV LC_ALL en_GB.UTF-8
ENV LANG en_GB.UTF-8
ENV LANGUAGE en_GB:en
#

# Composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
#

# Composer parallel install plugin
RUN composer global require hirak/prestissimo
#

RUN apt-get autoremove --purge \
&& apt-get -y clean \
&& rm -rf /var/lib/apt/lists/*

CMD ["php-fpm", "--nodaemonize"]

docker/php/php.ini


[PHP]
date.timezone=Europe/London
log_errors=On
error_reporting=E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors=Off
max_execution_time=60
memory_limit=256M

[opcache]
; http://symfony.com/doc/current/performance.html
opcache.enable_cli=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
realpath_cache_size=4096K
realpath_cache_ttl=600

docker/php/www.conf


[global]
daemonize=no

[www]

user=www-data
group=www-data

listen=dsa_nginx:9000

; Dynamicaly chooses how the process manager will control the number of child processes.
pm=dynamic
; The maximum number of child processes to be created.
; This option sets the limit on the number of simultaneous requests that will be served.
; Availalbe RAM in MB / Average RAM used by php-fpm processes in MB=max_children
; 1500MB / 30MB=50 (minus a bit)
pm.max_children=40
; The number of child processes created on startup.
; min_spare_servers + (max_spare_servers - min_spare_servers) / 2
pm.start_servers=2
; The desired minimum number of idle server processes.
pm.min_spare_servers=2
; The desired maximum number of idle server processes.
; 2 or 4 times of the CPU core
pm.max_spare_servers=4
; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries.
pm.max_requests=500

Build


$ docker-compose up -d --build

Test


Just run curl http://localhost:8081 command.


Logs


All the docker service logs (Nginx, PHP-FPM and MySQL) are accessible under var/log/docker folder of Symfony application.


Build information


Images


$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
inanzzz/dsa_php latest 6f0517c2fd66 17 minutes ago 469MB
inanzzz/dsa_nginx latest 84839a63faba About an hour ago 109MB
inanzzz/dsa_mysql latest 5752774a18bd About an hour ago 372MB
php 7.2.13-fpm 2bd622691e6e 5 days ago 371MB
nginx 1.15.7 568c4670fa80 2 weeks ago 109MB
mysql 5.7.24 ae6b78bedf88 4 weeks ago 372MB

Containers


$ docker ps
CONTAINER ID IMAGE COMMAND PORTS NAMES
dc072b8092c0 inanzzz/dsa_nginx:latest "nginx -g 'daemon of…" 0.0.0.0:8081->80/tcp dsa_nginx
0eeaad647c01 inanzzz/dsa_php:latest "docker-php-entrypoi…" 9000/tcp dsa_php
4ccfe75c4d3e inanzzz/dsa_mysql:latest "docker-entrypoint.s…" 3306/tcp, 33060/tcp dsa_mysql

Network


$ docker network ls
NETWORK ID NAME DRIVER SCOPE
87542b88228e inanzzz_default bridge local