Hi there. Today we are going to setup a Symfony project based on Docker. Microservices are very popular this day. I will show you how easily you can run your Symfony application on docker containers.

Setup docker containers

First of all, we need to think what we need in our application. I chose very basic setup with Nginx, Postgres as database server and RabbitMq. Moreover, I decided to use a docker-compose application to make all the setup and maintenance even easier. How to installing Docker and docker-compose you can find in the documentation.

The very first step is to add docker-compose.yml file. The file keeps information about docker containers we want to use and how looks relations between them. We will open some ports, too to be able to view our website and connect for example to Postgres or RabbitMq outside containers. Our new docker-compose.yml file looks as follows:

web:
  build: docker/nginx
  ports:
    - "8088:80"
  links:
    - php
  volumes:
    - ./:/app
 
php:
  build: docker/php-fpm
  volumes:
    - ./:/app
  working_dir: /app
  links:
    - postgres
    - rabbitmq
 
postgres:
  image: postgres
  volumes:
      - ./:/app
  ports:
  - "5433:5432"
 
rabbitmq:
  image: rabbitmq:3-management
  ports:
    - "5673:5672"
    - "15672:15672"

As you can see I added all required components. Some components use images (for example Postgres) and some of them use builds. What’s the difference? When we use a ready image the docker-compose will just download it. He does not have anything to do with it. They are provided by other developers via dockerhub. The builds say we will use some ready container but we want to add some tweaks to it to feet your needs. That’s what we will do next.

In the web container, we defined docker/nginx. We need to create a new folder with the same name in our project root directory. Inside of it create a new file named Dockerfile. It is the specific file where we define our container. In our case the file should look similar to:

# /docker/nginx/Dockerfile
FROM nginx:latest
COPY symfony.conf /etc/nginx/conf.d/symfony.conf

All we do here is just say we want to use the latest version of the Nginx and we want to copy symfony.conf file to Nginx configuration folder. The file does not exist, so we need to create it in path /docker/nginx/symfony.conf as follows:

server {
    server_name symfony.dev www.symfony.dev;
    root /app/web;
 
    location / {
        # try to serve file directly, fallback to app.php
        try_files $uri /app_dev.php$is_args$args;
    }
    # DEV
    # This rule should only be placed on your development environment
    # In production, don't include this and don't deploy app_dev.php or config.php
    location ~ ^/(app_dev|config)\.php(/|$) {
        add_header 'Access-Control-Allow-Origin' '*';
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param  SCRIPT_FILENAME  $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
    }
    # PROD
    location ~ ^/app\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        # When you are using symlinks to link the document root to the
        # current version of your application, you should pass the real
        # application path instead of the path to the symlink to PHP
        # FPM.
        # Otherwise, PHP's OPcache may not properly detect changes to
        # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126
        # for more information).
        fastcgi_param  SCRIPT_FILENAME  $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        # Prevents URIs that include the front controller. This will 404:
        # http://symfony3.dev/app.php/some-path
        # Remove the internal directive to allow URIs like this
        internal;
    }
}

We define a new virtual host in Nginx to work well with Symfony.

Our web server is configured, so we need to install PHP. I will install PHP7 using php-fpm. Do not wait end create a new file /docker/php-fpm/Dockerfile.

FROM php:7.0-fpm
 
RUN apt-get update && buildDeps="libpq-dev libzip-dev" && apt-get install -y $buildDeps git  nano wget --no-install-recommends
RUN docker-php-ext-install pdo pdo_pgsql pgsql zip bcmath
 
RUN wget https://getcomposer.org/composer.phar && mv composer.phar /usr/bin/composer && chmod +x /usr/bin/composer
 
WORKDIR /app

We use PHP7 FPM image and install required dependencies. In 6th row, we download the latest version of Composer.

Setup Symfony project

Now, we need to install RabbitMq bundle to our Symfony application. To do that register the bundle by adding it to the kernel.

public function registerBundles()
{
    $bundles = array(
        new OldSound\RabbitMqBundle\OldSoundRabbitMqBundle(),
    );
}

We are almost ready to run the services and see Symfony welcome page. To do that we need to build the containers (Nginx and PHP7) and run them. All we need is type in the console following command:

$ docker-compose build

It will download all the images and build them (if needed). Now we need to add some configurations to Symfony itself. Update your app/config/parameters.yaml.dist file with the following content

parameters:
    database_host:     postgres
    database_port:     ~
    database_name:     symfony
    database_user:     postgres
    database_password:
    # ...some other staff
    rabbit_mq_host: 'rabbitmq'
    rabbit_mq_port: 5672
    rabbit_mq_user: 'guest'
    rabbit_mq_password: 'guest'
    rabbit_mq_prefix: 'forms_'

As you can see the host for Postgres is ‘postgres’. It is the same name we used in docker-compose.yml file. The name we provided there is the name of the host in the network between containers. We granted access to the host thanks to ‘links’ parameters.

php:
  build: docker/php-fpm
  volumes:
    - ./:/app
  working_dir: /app
  links:
    - postgres
    - rabbitmq

Now you can see that the PHP container has access to postgres and rabbitmq containers and can connect to them using TCP/UDP protocols. Cool, yeah? We did the same thing with Nginx and PHP. We granted Nginx access to connect to PHP container. All the credentials to the services are default.

OK, it is time to create another configuration file in app/config/rabbit.yml. I like separating configurations files into parts because it improves readability of the files. You can read how to create proper settings in the docs. Oh, remember to add the file to the app/config/config.yml.

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: services.yml }
    - { resource: rabbit.yml }

Now, add IP ‘172.17.0.1’ to an array in the app_dev.php file to have access to the development version of you application. The next step is installing all Symfony dependencies and RabbitMq bundle.

$ docker-compose run php composer require php-amqplib/rabbitmq-bundle

Notice we use Composer installed in the container. It is needed because of file permissions to vendor/cache etc folders. Please remember about it and always use Composer in the Docker container.

Ready? Set! Go!

Now it is time to run all the stuff! It cannot be easier. Just type:

$ docker-compose run -d

The ‘d’ parameter says we want to run it as a daemon (in the background). We are ALMOST ready to see the project in our browser. In Nginx, we defined virtual host with domain symfony.dev. We need to add the domain to our /etc/hosts file, but what IP should I put there? We need to get container ID to identify the container with Nginx installation and get it’s IP. Here is how you can do this.

$ docker ps # get list of running containers

Find the container with ‘web’ in its name. In the first column, you will get its ID. All you need is copy it and paste to the command below instead of THE_ID text.

$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' THE_ID

When you do that you will see the IP we need. Just copy it and put to the /etc/hosts file. The only thing left is to open your browser and type http://symfony.dev.

Summary

As you can see it is not so difficult to run Symfony application in docker containers. You can try to add some other stuff like Logstash, Kibana and so on. I hope it will help you in development! If you have any questions or suggestions do not be shy, just ask.

About the author

Bartłomiej Kiełbasa

Bartłomiej Kiełbasa

Hi! I'm Bartek. I'm a PHP developer but other languages are not scary to me. My hobby is security and I try to learn as much as it's possible how to not be hacked. I'd ike to know how things work. After hours I like playing Dota2 and watching Dragon Ball :) If you will have any question, feel free to ask.