Martin Pham
Posted on December 30, 2019
We were playing with Kubernetes last week, however the project was just a small PHP file with phpinfo() function call, no big deal.
Today my colleague asked me to guide him a bit on Docker, because he’d like to try it with a real world example: Developing a Symfony project. So let’s take a look at this, it's quick, easy and fun!
Symfony uses Composer to manage its dependencies and scripts, namespaces,.. with a file named composer.json. Dependencies will be download to a directory called vendor.
Focus on development, we'd like to create a ready configured & isolated environment, so anyone can clone the repository and run the application easily. So we’re gonna use 3 containers:
- MySQL, with mounted volume for data persistent
- PHP-FPM, with mounted volume for application’s code
- NGINX, with mounted volumes for configurations, logs, and share mounted volume with PHP-FPM for application’s assets
We will also need to use some environment variables for containers’ parameters, like database credentials, application secret key,…
We’re gonna use Docker-Compose to put configurations and run all containers.
Project
│
├── docker-compose.yml
│
├── database/
│ ├── Dockerfile
│ └── data/
│
└── php-fpm/
│ └── Dockerfile
│
├── nginx/
│ ├── Dockerfile
│ └── nginx.conf
│
└── logs/
└── nginx/
MySQL Database
Let’s just create a MariaDB container
# docker/database/Dockerfile
FROM mariadb:latest
CMD ["mysqld"]
EXPOSE 3306
Explaination
- We use MariaDB official image
- Run mysqld to start the server
- Expose port 3306 for database connection
PHP-FPM
With PHP-FPM container, we’d like to install dependencies and run database migrations at start. So we need to install the PDO MySQL extension, then composer, and then Symfony migration script.
However, it could be a problem if we run the migration before the MySQL server is ready. We will need to handle this also.
With Docker-compose, we can specify a depends_on configuration to tell it wait for another container. But it doesn’t mean Docker-compose will wait until the MySQL server is ready, it only waits until the MySQL container is up.
Fortunately, with the help from wait-for-it script, we can try to wait until the MySQL container’s port 3306 is Open (Or you can even try to wait until you can connect to the MySQL using credentials).
# docker/php-fpm/Dockerfile
FROM php:fpm-alpine
COPY wait-for-it.sh /usr/bin/wait-for-it
RUN chmod +x /usr/bin/wait-for-it
RUN apk --update --no-cache add git
RUN docker-php-ext-install pdo_mysql
COPY --from=composer /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
CMD composer install ; wait-for-it database:3306 -- bin/console doctrine:migrations:migrate ; php-fpm
EXPOSE 9000
Explaination
- We use PHP-FPM offical image
- Copy wait-for-it script into the container
- Allow execution for wait-for-it
- Add git for dependencies installation
- Install PHP PDO MySQL
- Take composer file from Composer official image
- Set working dir to /var/www
- Install dependencies, then wait until the MySQL container is Online to run migration script. Finally, run php-fpm to start the server
- Expose PHP-FPM port (9000)
NGINX
Okey, this part is a bit complex, we’re gonna create the NGINX configuration file, the PHP-FPM proxy, and a separated file for default NGINX site.
First the Dockerfile definition
# docker/nginx/Dockerfile
FROM nginx:alpine
WORKDIR /var/www
CMD ["nginx"]
EXPOSE 80
Explaination
- As above, we use NGINX official image
- Set working dir to /var/www, the same directory with PHP-FPM since we’re gonna share this with a mounted volume
- Start nginx
- Expose the port 80 for web
Now, an NGINX server configuration
# docker/nginx/nginx.conf
user nginx;
worker_processes 4;
daemon off;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-available/*.conf;
}
An NGINX – PHP-FPM configuration
# docker/nginx/conf.d/default.conf
upstream php-upstream {
server php-fpm:9000;
}
And an NGINX site’s configuration
# docker/nginx/sites/default.conf
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name localhost;
root /var/www/public;
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_pass php-upstream;
fastcgi_index index.php;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 600;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Docker-Compose configuration
We have 3 container definitions, now we just need to setup a Docker-compose configuration to connect all togethers:
# docker/docker-compose.yml
version: '3'
services:
database:
build:
context: ./database
environment:
- MYSQL_DATABASE=${DATABASE_NAME}
- MYSQL_USER=${DATABASE_USER}
- MYSQL_PASSWORD=${DATABASE_PASSWORD}
- MYSQL_ROOT_PASSWORD=${DATABASE_ROOT_PASSWORD}
ports:
- "3306:3306"
volumes:
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
- ./database/data:/var/lib/mysql
php-fpm:
build:
context: ./php-fpm
depends_on:
- database
environment:
- APP_ENV=${APP_ENV}
- APP_SECRET=${APP_SECRET}
- DATABASE_URL=mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@database:3306/${DATABASE_NAME}?serverVersion=5.7
volumes:
- ../src:/var/www
nginx:
build:
context: ./nginx
volumes:
- ../src:/var/www
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/sites/:/etc/nginx/sites-available
- ./nginx/conf.d/:/etc/nginx/conf.d
- ./logs:/var/log
depends_on:
- php-fpm
ports:
- "80:80"
And a sample environment variables:
# docker/.env
DATABASE_NAME=symfony
DATABASE_USER=appuser
DATABASE_PASSWORD=apppassword
DATABASE_ROOT_PASSWORD=secret
APP_ENV=dev
APP_SECRET=24e17c47430bd2044a61c131c1cf6990
Symfony
Let’s proceed to the Symfony installation:
$ symfony new src
Play time
Everything is setup correctly! Let’s play with our containers!
$ docker-compose up
When those containers are ready, you can start to open http://localhost, you will see a Symfony 5 welcome screen. Everything works perfectly, have fun!
I’ve create a repository for all the files we talked above https://gitlab.com/martinpham/symfony-5-docker
Posted on December 30, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.