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 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 Docker documentation
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 the article of our CEO Piotr.
I assume you already have Docker Engine and Docker Compose 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 check here.
version: '3.8'
MySql database in Docker container
Let's start by defining the first service, the database. First, the name, I chose “db”:
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 search engine.
In my case, the project uses MySql version 5.7 and such an image is also available so I could just use its name:
version: '3.8'
services:
db:
image: mysql:5.7
As per the documentation 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
.
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.
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.
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:
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:
FROM php:7.4-apache
Then I enabled Apache mod_rewrite
:
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 documentation. In the meantime, I also deleted files that are unnecessary in the container, they would only take up space.
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 Composer to manage dependencies in our application, so I installed in the container:
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:
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:
WORKDIR /var/www
And last command to run Apache in the container foreground:
CMD ["apache2-foreground"]
To sum up, we have such Dockerfile for web container:
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.
But wait a minute. Let's see if it really works:
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
#!/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.
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:
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 here :)
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 Docker Deep Dive
Top comments (0)