Simple Docker setup for Symfony project
Tags: docker, symfony, php
Published: 2022-04-15 13:43:47

It is no longer the case at Accesto that we work on projects without Docker. Of course, projects are not born dockerized, someone has to do it. Sometimes I do. I am a PHP developer and I work the most with [Symfony framework](https://accesto.com/blog/what-is-php-framework-symfony-explained-for-executives/) on a daily basis. In this article, I will present an example development **Docker configuration for the Symfony** project.

## So let's use a Docker in a Symfony project.

When it comes to Docker setup, **there is no one-size-fits-all solution**. It always depends on particular project needs. But some good practices should be followed, for example, those described in the official <a rel="nofollow" href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/" target="_blank">Docker documentation</a>

Let's imagine we take over an existing project, a simple one, **PHP, Symfony, MySQL database**, that's all. But there is no Docker. So instead of installing a local web server, the appropriate PHP version, the appropriate database engine, etc., the first thing to do is ... dockerize!

Why should I spend time on dockerizing first? Because I will do it once, and everyone working on this project will benefit from it. You can read more about why it is worth using the Docker in <a href="https://accesto.com/blog/what-is-docker-and-why-to-use-it/">the article</a> of our CEO Piotr. 

I assume you already have <a rel="nofollow" href="https://docs.docker.com/engine/install/" target="_blank">Docker Engine</a> and <a rel="nofollow" href="https://docs.docker.com/compose/install/" target="_blank">Docker Compose</a> installed, if not, you should do so.

Now let's focus on what we need. This project is currently running on Apache, PHP 7.4 and MySQL 5.7.

## Configure Docker using docker-compose.yml

I have added the _docker-compose.yml_ file in the main project directory, and I defined our first container in it - the one with the database. File _docker-compose.yml_ is used by (Docker) Compose to manage and run Docker applications.

At the beginning of the file, you can (from version v1.27.0 it is optional) define the version of the configuration. Different versions of Docker Engine + Docker Compose work with different versions of the configuration, which you can <a rel="nofollow" href="https://docs.docker.com/compose/compose-file/" target="_blank">check here</a>.

```yaml
version: '3.8'
```

## MySql database in Docker container

Let's start by defining the first service, the database. First, the name, I chose “db”:

```yaml
version: '3.8'
 
services:
    db:
```

After that I had to define what _db_ service is. For example, we can use the _image_ option. As an _image_, I had to select the appropriate Docker image. **We can use ready-made images** available on the web for this. Most of the tools come with Docker images. And to search for these images, we can use the <a rel="nofollow" href="https://hub.docker.com/" target="_blank">search engine</a>.

In my case, the project uses MySql version 5.7 and such an image is also available so I could just use its name:

```yaml
version: '3.8'
 
services:
    db:
        image: mysql:5.7
```

As per the <a rel="nofollow" href="https://hub.docker.com/_/mysql" target="_blank">documentation</a> for this image, some things in this container can be **configured using environment variables**.

One variable is mandatory, this is `MYSQL_ROOT_PASSWORD`. As you might guess, this is the MySql root superuser password. But there are a few other variables that are helpful, I used the following: `MYSQL_DATABASE`, a database with this name will be created during image startup. If we define `MYSQL_USER`, `MYSQL_PASSWORD`, a user with this name and password will be created, and he will have granted superuser access to the database defined in `MYSQL_DATABASE`.


```yaml
version: '3.8'
 
services:
    db:
        image: mysql:5.7
        environment:
            - MYSQL_ROOT_PASSWORD=somerootpass
            - MYSQL_PASSWORD=somepass
            - MYSQL_DATABASE=dockerizeme_db
            - MYSQL_USER=someuser
```

## Symfony Docker image

But **there are no ready-made images for everything**. For example, there is no ready image for a web server configured according to the needs of our application.

So while defining the container for our web server with PHP, I did not use the _image_ option with the name of the ready image, but I used the _build_ option with directory (.) that contains the file (_Dockerfile_) from which the image will be built. But about _Dockerfile_ soon.

```yaml
version: '3.8'
 
services:
    db:
        # cut for readability, see above
    web:
        build: .
```

Going further. It would be nice to add a basic Apache configuration. I added the _docker_ directory in the main project directory, where I will keep files related to the Docker environment configuration. Then, in that directory, I added the _apache.conf_ file. This file is a very basic configuration of a web server.

```
<VirtualHost *:80>
    DocumentRoot /var/www/public
 
    <Directory /var/www/public>
        AllowOverride None
        Order Allow,Deny
        Allow from All
 
        <IfModule mod_rewrite.c>
            RewriteEngine On
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteRule ^(.*)$ index.php [QSA,L]
        </IfModule>
    </Directory>
</VirtualHost>
```

Let's make our container accessible "from the outside". I used the _ports_ option for this. Port 80 from the inside of the container will be exposed to the outside at port 8080.

```yaml
version: '3.8'
 
services:
    db:
        # cut for readability, see above
    web:
        # cut for readability, see above
        ports:
            - 8080:80
```

Why didn't I put it on port 80 but 8080? Because we often have Apache running locally on port 80, so we would have a conflict and the Docker container wouldn't start.

Finally docker-compose.yml looks like this:

```yaml
version: '3.8'
 
services:
    db:
        image: mysql:5.7
        environment:
            - MYSQL_ROOT_PASSWORD=somerootpass
            - MYSQL_PASSWORD=somepass
            - MYSQL_DATABASE=dockerizeme_db
            - MYSQL_USER=someuser
    web:
        build: .
        ports:
            - 8080:80
```
 
## _Dockerfile_ for Apache and PHP part

Now we need to take a step back. Let's go into the details of our web image. To define it, I added a _Dockerfile_ file to the main project directory. Of course, we will use the ready-made public image as a starting point, and slightly adjust it to our needs. I used the _php:7.4-apache_ image as a base.

At the beginning of _Dockerfile_, I defined the Docker image which is our base:

```dockerfile
FROM php:7.4-apache
```

Then I enabled Apache `mod_rewrite`:

```dockerfile
RUN a2enmod rewrite
```

After that I did the classic update, several packages installed such as `libzip-dev` (needed for zip which is needed for composer) `git wget` via apt-get install. And some extensions such as `pdo mysqli pdo_mysql zip` via docker-php-ext-install mechanism, more about that you can find in base image <a rel="nofollow" href="https://hub.docker.com/_/php" target="_blank">documentation</a>. In the meantime, I also deleted files that are unnecessary in the container, they would only take up space.

```dockerfile
RUN apt-get update \
  && apt-get install -y libzip-dev git wget --no-install-recommends \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 
RUN docker-php-ext-install pdo mysqli pdo_mysql zip;
```

The application uses <a rel="nofollow" href="https://getcomposer.org/" target="_blank">Composer</a> to manage dependencies in our application, so I installed in the container:

```dockerfile
RUN wget https://getcomposer.org/download/2.0.9/composer.phar \ 
    && mv composer.phar /usr/bin/composer && chmod +x /usr/bin/composer
```

I copied the Apache configuration file to the appropriate place in the container, and our project files to _/var/www_ in the container:

```dockerfile
COPY docker/apache.conf /etc/apache2/sites-enabled/000-default.conf
COPY . /var/www
```

Instruction WORKDIR sets the working directory for other instructions, for example for the RUN command:

```dockerfile
WORKDIR /var/www
```

And last command to run Apache in the container foreground:

```dockerfile
CMD ["apache2-foreground"]
```

To sum up, we have such _Dockerfile_ for web container:

```dockerfile
FROM php:7.4-apache
 
RUN a2enmod rewrite
 
RUN apt-get update \
  && apt-get install -y libzip-dev git wget --no-install-recommends \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 
RUN docker-php-ext-install pdo mysqli pdo_mysql zip;
 
RUN wget https://getcomposer.org/download/2.0.9/composer.phar \
    && mv composer.phar /usr/bin/composer && chmod +x /usr/bin/composer
 
COPY docker/apache.conf /etc/apache2/sites-enabled/000-default.conf
COPY . /var/www
 
WORKDIR /var/www
 
CMD ["apache2-foreground"]
```

Is that all? **As for the minimum, yes!** Now if I run the `docker-compose up -d` in project directory in the console, the containers will be created and the whole project will start running.

![Run Symfony on docker](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fp88dp721pbzuk5fbzp.gif)

But wait a minute. Let's see if it really works:

![Run Symfony on docker 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zayda6x9xjju6kv0l612.png)

Of course not ;). Our project needs a few more steps. For example, what about `composer install`? Database migrations? Now we can describe these steps in the Readme of the project. It would be something like “enter the container (_docker-compose exec web bash_) and run composer install in the container, then run migrations (_bin/console doc:mig:mig_)”.

## Improvements in startup a dockerized project

We can try to "automate" these steps, for example by using Entrypoint. Using Entrypoint is not the best solution, but it will **facilitate development** at this stage. Let's assume that I want to run every time the container is started:

- composer install
- new database migrations to be loaded  
- fresh fixtures (dummy/test data) in the database.  

So I added _entrypoint.sh_ in _docker_ directory with a simple script to do it


```bash
#!/usr/bin/env bash
 
composer install -n
bin/console doc:mig:mig --no-interaction
bin/console doc:fix:load --no-interaction
 
exec "$@"
```

Then I had to slightly modify our _Dockerfile_.

```dockerfile
FROM php:7.4-apache
 
# … cut for readability
 
COPY docker/apache.conf /etc/apache2/sites-enabled/000-default.conf
COPY docker/entrypoint.sh /entrypoint.sh
 
# … cut for readability
 
RUN chmod +x /entrypoint.sh
 
CMD ["apache2-foreground"]
 
ENTRYPOINT ["/entrypoint.sh"]
```

I COPY the new script file to the container and add executable permissions to that file. At the end of the _Dockerfile_, I defined that the file is Entrypoint

Now I had to rebuild the Docker image to apply these changes `docker-compose build web`, and restart the containers. Let's check our application:

![app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylquzpo6tidq4y72v7f3.gif)

It works! Now, anyone who downloads this project and has Docker **can simply run it**.

Can you also work with Docker on a Mac? You can read about it in the latest post by Krzysiek <a href="https://accesto.com/blog/docker-on-mac-how-to-speed-it-up/">here</a>  :)

Docker is a powerful tool, I only showed you a minimal snippet that will allow you to start your project in a box. You can find more practical tips and **advanced Docker optimization techniques** in our **free e-book** <a href="https://accesto.com/books/docker-deep-dive/">Docker Deep Dive</a>

<a href="https://accesto.com/books/docker-deep-dive/">
![Docker Deep Dive Book](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rlvwyhxbekczgalcmvi.png)
</a>