DEV Community

Cover image for Symfony 5 development with Docker
Martin Pham
Martin Pham

Posted on • Updated on

Symfony 5 development with Docker

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

Alt Text

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/
Enter fullscreen mode Exit fullscreen mode

MySQL Database

Let’s just create a MariaDB container

# docker/database/Dockerfile

FROM mariadb:latest
CMD ["mysqld"]
EXPOSE 3306
Enter fullscreen mode Exit fullscreen mode

Explaination

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

An NGINX – PHP-FPM configuration

# docker/nginx/conf.d/default.conf

upstream php-upstream {
    server php-fpm:9000;
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Symfony

Let’s proceed to the Symfony installation:

$ symfony new src
Enter fullscreen mode Exit fullscreen mode

Play time

Everything is setup correctly! Let’s play with our containers!

$ docker-compose up
Enter fullscreen mode Exit fullscreen mode

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

Top comments (19)

Collapse
 
danseely profile image
Dan Seely

Great writeup! This is going to help me out a ton.

After reading through the article and looking at your example repo, I think the directory structure at the top of this article would look more like this (i.e. dedicated docker and src directories):

Project
│
├── docker
├──── docker-compose.yml
│
├──── database/
│   ├──── Dockerfile
│   └──── data/
│
└──── php-fpm/
│   └──── Dockerfile
│
├──── nginx/
│   ├──── Dockerfile
│   └──── nginx.conf
│
└──── logs/
│   └──── nginx/
├── src
├──── <Symfony app>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
b3nb1 profile image
b3nb1 • Edited

For those which rather want to use postgresql instead of mariadb.

# docker/database/Dockerfile
FROM postgres:latest

ENV POSTGRES_USER <your user>
ENV POSTGRES_PASSWORD <your password>
EXPOSE 3306

And I had to add

RUN apk add --no-cache bash

in php-fpm dockerfile (due to this warning: env: can't execute 'bash': No such file or directory).

Collapse
 
eelcoverbrugge profile image
Eelco Verbrugge

Greatly explained setup, thanks! How to downgrade php to 7.4? I tried replacing the first line in docker/php-fpm/Dockerfile to FROM php:7.4-fpm-alpine and fired up my container but didn't work. docker exec -it docker_php-fpm_1 php -v outputs PHP 8.0.9 (cli). Any thoughts?

Collapse
 
stevencarre profile image
Steven Carré

Hi, great post!
this is where i get an error:
when i try docker-compose up, i get this error
Error starting userland proxy: listen tcp 0.0.0.0:80: bind: address already in use
What am I doing wrong?
Any help appreciated

Collapse
 
samikerb profile image
samikerb

Hello,

thank you for this amazing post,

i have an issue with login, once i submit to login i got this error :

php-fpm_1 | [21-Sep-2020 16:45:16] WARNING: [pool www] child 38 exited on signal 9 (SIGKILL) after 318.447129 seconds from start
php-fpm_1 | [21-Sep-2020 16:45:16] NOTICE: [pool www] child 50 started

any idea ?

Collapse
 
samikerb profile image
samikerb

2020/09/21 16:48:50 [error] 29#29: *37 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 192.168.0.1, server: localhost, request: "POST /login HTTP/1.1", upstream: "fastcgi://192.168.0.3:9000", host: "localhost:1609", referrer: "localhost:1609/login"

Collapse
 
jdf1976 profile image
jdf1976

Hello nice Tutorial.
I gott a Error while Docker-compose

=> ERROR [stage-0 2/7] COPY wait-for-it.sh /usr/bin/wait-for-it 0.0s

[stage-0 2/7] COPY wait-for-it.sh /usr/bin/wait-for-it:

failed to compute cache key: "/wait-for-it.sh" not found: not found
ERROR: Service 'php-fpm' failed to build

can someone help me to solve that problem?

Collapse
 
1n53c profile image
Rene Glasow

Please have a look at the Gitlab Repo mentioned in the article. There you will find the wait-for-it.sh. You have to add it to your code. It's not included in the article.

Collapse
 
bronkes profile image
bronkes

UPDATE 31.07.2021
if someone having the problem with error from apline:
/bin/sh: Operation not permitted
or
*some_command* returned a non-zero code: 2

just change first line in docker/php-fpm/Dockerfile to:
FROM php:8.0-fpm-alpine3.13

They mess something in update 3.14, changing to older version fixes that

Collapse
 
abelardoit profile image
abelardoit • Edited

Hi there!
I am a beginner developer. I downloaded your project from your repo for learning purposes.
I use Maker Bundle to automatically create an user.
When I run the command (docker and mysql are already running):
php bin/console make:migration

this error is shown several times in a row:
SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Temporary failure in name resolution

Could be possible that this bundle is unable to know where my database is running? How could I solve this issue?

Any help would be welcome. Thanks for your excellent article and cheers.

Collapse
 
fadilxcoder profile image
fadilxcoder

You should connect to you docker terminal to run the above command... It should be something like docker exec -it docker_container_name ash. Your docker_container_name should be listed in docker ps -a

Collapse
 
arthurguillerm profile image
Arthur Guillerm

Excellent article !

Collapse
 
martinpham profile image
Martin Pham

Thanks! Glad you liked it!

Collapse
 
bkrnetic profile image
bkrnetic

Great article!

Would be great if I was able to enter container bash by running

docker-compose exec nginx bash
Enter fullscreen mode Exit fullscreen mode

Is it possible to update Dockerfile to enable that?

Collapse
 
nawaaugustine profile image
Nawa Augustine

You can try

docker exec -it nginx sh