Create a Docker instance : Symfony / MariaDB / Nginx

Pierre Belin
7 min readJun 23, 2021

--

If you are here, you want to improve your skill in Docker.

I imagine you are actually developing with a third-party application as WAMP/MAMP.

It was a good setup, 5 years ago…

Now, this is a typical bad practice!

Why?

Using WAMP/MAMP

The list is far from short :

  • You depend on WAMP installation…
  • The SSL support sucks…
  • The port handling isn’t easy to manage
  • You’re 99% sure you are not iso with the production

Directly on Linux

The only thing I can disagree with is that your computer doesn’t containerize your project.

After several coding years, you have installed tons of extensions/packages. It’s hard to determine which extensions your project needs.

I know and I agree, you have to know what each extension does. But are you 100% sure you didn’t miss anything?

In my opinion, I prefer not to leave anything to chance.

The better you containerize each project, the easier you create your production environment. It’s important to have an iso environment between development and production.

Don’t worry, it’s not that difficult to create your own environment with Docker.

For me, Docker is a must-have for every developer.

Here, I’ll show you how to create a Symfony/MariaDB/Nginx environment with Docker!

Let’s start a new project called my myapp!

Initiate Docker with Docker Compose

The first thing to do is to install Docker. The installation contains Docker and Compose, the 2 tools that we’ll use day!

When the installation is over, we start with the first step create the file docker-compose.yml

It will contain all the services of our stack!

#docker-compose.yml
version: '3.7'
services:
...
networks:
myapp:

Check out your Docker version (docker -v) to validate if your Compose version is correct: https://docs.docker.com/compose/compose-file/

This is how Compose works.

Compose uses the docker-compose.yml to instantiate containers. As the opposite of Docker (without Compose), you can instantiate linked containers at the same time.

It allows you to create a poll of containers for a unique function, and to delete them at the same time when you’ll not need them anymore. We call them services.

Each service is instantiated from an image from Docker Hub (it centralizes all public images).

Moreover, each service has its own parameters (image, environment, ports, network…) which allow to configure them.

Database: MariaDB

As a database, we’ll use the latest images from MariaDB.

services:
db:
image: mariadb:latest # The image from Docker Hub
container_name: myapp_db
environment: # This is my settings, change them as you prefer
MYSQL_ROOT_PASSWORD: PASSWORD_ROOT
MYSQL_DATABASE: DATABASE_NAME
MYSQL_USER: USER
MYSQL_PASSWORD: PASSWORD
networks: # Allows to communicate with other services with the same network
- myapp

Don’t forget to change all variables with your own.

And you can also add the service phpMyAdmin if you want an interface to access your database from your browser:

...
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
container_name: myapp_phpmyadmin
ports:
- 8080:80
environment:
PMA_HOST: db # Same name that the database service, here db
MYSQL_ROOT_PASSWORD: PASSWORD_ROOT # Same password as MYSQL_ROOT_PASSWORD
networks:
- myapp # You see here the same network as the service db

It’s all we need to have a database and a front interface!

PHP

The PHP image is a bit more complex than those seen previously.

There is no official image that contains all Symfony packages.

So we have to create our own Docker image with all needed packages (composer included) in the file Dockerfile-php. It's based on the image php:7.4-fpm, with additional installations.

#Dockerfile-php
FROM php:7.4-fpm
RUN apt-get update && apt-get install -y
# You can check all possible installation here : https://gist.github.com/chronon/95911d21928cff786e306c23e7d1d3f3
RUN apt-get install -y --no-install-recommends \
git \
zlib1g-dev \
libxml2-dev \
libzip-dev \
libpq-dev \
nano \
&& docker-php-ext-install \
zip \
intl \
pdo \
mysqli \
pdo_mysql \
opcache
# Install Composer !
RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer
RUN curl -sS https://get.symfony.com/cli/installer | bash
RUN mv /root/.symfony/bin/symfony /usr/local/bin/symfony
# Set the default directory inside the container
WORKDIR /var/www/symfony

And

#docker-compose.yml
...
php:
container_name: myapp_php
depends_on:
- db
build:
context: .
dockerfile: Dockerfile-php
environment: # We set some environments variables to facilitate debug
APP_ENV: dev
APP_DEBUG: 1
volumes:
- files:/var/www/symfony # It has to match with the WORKDIR inside the docker file
networks:
- myapp # Still same network

2 lines are here :

  • build: docker-compose will build our custom image in this container
  • volumes: link a volume name to a path inside the image.

The important thing here is that you want to manipulate the exact same files. So you have to create a link between your local application and the Docker PHP.

This link is volumes. Add the following code at the end of the file.

#docker-compose.yml
...
volumes:
files: # Same name as inside the container php
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: LOCAL/PATH/TO/MYAPP # Change with the project path on your computer

PHP and Symfony are all set!

Nginx

Finally, we end by setting Nginx to create access to our Symfony project.

....
nginx:
container_name: myapp_nginx
depends_on:
- php # We need to load PHP for the Nginx configuration file
build:
context: .
dockerfile: Dockerfile-nginx
ports:
- 8000:80 # Redirect Docker port 80 to localhost port 8000. So you'll access to Nginx with localhost:8000
- 8443:443 # Same for HTTPS
networks:
- myapp

Exactly the same way as the PHP image, we’ll create our own Nginx image to include Let’s Encrypt support if you need to develop with HTTPS protocol.

#Dockerfile-nginx
FROM nginx:latest
COPY ./build/nginx/default.conf /etc/nginx/conf.d/RUN apt update
RUN apt install -y --no-install-recommends \
python3-acme \
python3-certbot \
python3-mock \
python3-openssl \
python3-pkg-resources \
python3-pyparsing \
python3-zope.interface
RUN apt install -y --no-install-recommends python3-certbot-nginx

And we create a custom Nginx configuration file as the default configuration.

#build/nginx/default.conf
server {
listen 80;
#listen 443 ssl;
#listen [::]:443 ssl;
#ssl_certificate /etc/ssl/certs/localhost.crt;
#ssl_certificate_key /etc/ssl/private/localhost.key;
root /var/www/symfony/public;
server_name localhost;
index index.php index.html;location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php$ {
fastcgi_pass php:9000; # Same name as the PHP service (php)
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
error_log /var/log/nginx/myapp.error.log;
access_log /var/log/nginx/myapp.access.log;
}

The final docker-compose.yml

Wait, I added some parameters to make it fully customizable. To make easier the database dump, we also create a volume for SQL files.

So copy this one to get the final file.

version: '3.7'services:
db:
image: mariadb:latest # The image from Docker Hub
container_name: myapp_db
environment: # This is my settings, change them as you prefer
MYSQL_ROOT_PASSWORD: PASSWORD_ROOT
MYSQL_DATABASE: DATABASE_NAME
MYSQL_USER: USER
MYSQL_PASSWORD: PASSWORD
volumes:
- sql:/var/lib/mysql/
networks: # Allows to communicate with other services with the same network
- myapp
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
container_name: myapp_phpmyadmin
ports:
- 8080:80
environment:
PMA_HOST: db # Same name that the database service, here db
MYSQL_ROOT_PASSWORD: PASSWORD_ROOT # Same password as MYSQL_ROOT_PASSWORD
networks:
- myapp # You see here the same network as the service db
php:
container_name: myapp_php
depends_on:
- db
build:
context: .
dockerfile: Dockerfile-php
environment:
APP_ENV: dev
APP_DEBUG: 1
volumes:
- files:/var/www/symfony
networks:
- myapp
nginx:
container_name: myapp_nginx
depends_on:
- php # We need to load PHP for the Nginx configuration file
build:
context: .
dockerfile: Dockerfile-nginx
ports:
- 8000:80 # Redirect Docker port 80 to localhost port 8000. So you'll access to Nginx with localhost:8000
- 8443:443 # Same for HTTPS
networks:
- myapp
networks:
myapp:
volumes:
files: # Same name as inside the container php
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: LOCAL/PATH/TO/MYAPP # Change with the project path on your computer
sql:
driver: local
driver_opts:
type: 'none'
o: 'bind'
device: LOCAL/PATH/TO/MYSQL # Change with the project path on your computer

Start the Docker instance

The next step is to build our containers from the docker-compose.yml

Warning: you need to be on the same path as the file to launch the command! Also, take care of indent

docker-compose build

Also, pay attention to the indentation if you don’t want to waste hours for silly mistakes.

I recommend you add the parameter --no-cache at the end of the command line.

When you build, docker-compose doesn’t rebuild modification outside the docker-compose.yml file. If you want, for example, to change the file Dockerfile-php or default.conf, it won't rebuild.

Now, you can launch: docker-compose up -d

Here we are, we have access to our instance NGINX: http://localhost:8000 and even to our PHPMyAdmin: http://localhost:8080

You can start and stop your containers directly with the Docker UI installed, or you can use those commands docker-compose start docker-compose stop.

To delete your containers: docker-compose down. It won't delete your project files linked to Docker since you created a volume.

Launch command in Docker instance

Last and not the less, the command to access our PHP container bash :

docker exec -it myapp_php bash

It will connect you to the container bash. Now you can launch your Symfony command directly in your Docker.

symfony new myapp

Last thing: copy the entire content myapp in the parent folder symfony (to match the Nginx configuration file).

Now you have your environment Docker for Symfony! Let’s code :)

Isn’t it beautiful?

Originally published at https://blog.pierrebelin.fr on June 23, 2021.

--

--

Pierre Belin

.NETCore Developer / DevOps — Interested in all ways to simplify complex ideas ! Blogger for GoatReview.com