How setup and run Symfony with Docker.

John in 't Hout
7 min readSep 24, 2021

This is part of an series for creating an SaaS scaffolding tool called Sassy. This scaffolding tool is inspired on the Laravel Spark solution.

Setting up your environment

Composer && symfony-cli

There are 2 options I would suggest using “composer” or “symfony-cli” with if you prefer to use composer instead you could skip this step.

First you need to install symfony-cli for an more in depth information of this tool please take a look at the docs.

Linux:

wget https://get.symfony.com/cli/installer -O - | bash

MacOS:

curl -sS https://get.symfony.com/cli/installer | bash

Windows:

For windows download this EXE file and follow the wizard.

Once the installation of the cli tool has succeeded we can make use of the following command to check if our system has all the necessary requirements to start using the tool.

symfony check:requirements

What will give the following output if everything is correctly setup

Docker
For the docker environment is truly dependent on what OS you are using you can follow the docs of docker to see what the steps are for your OS. You can find the Docker docs here.

Docker-compose
Next we need Docker-compose this also depends allot on your OS follow these docs for your OS.

Creating our Symfony application.

Next we want to scaffold our symfony application we can do this by using the just installed symfony-cli tool and execute the following in our terminal:

symfony new --full SaasApp

The --full flag tells our scaffolding tool that we want to use the complete Symfony (web skeleton) that contains all the basic composer packages that we are going to need for our future articles. A few examples of packages: database, debugging, twig (templating) etc. This should load a bit and you should get the following output:

Open the newly created folder in my case SaasApp in your favorite IDE.

Creating our docker-compose file

A docker-compose file is the orchestra-tor of our application is contains the configuration, services and file mappings that shapes application. In the root of your project folder create an file docker-compose.yaml and lets start by adding the first few rules:

version: "3.4"

services:
web:
image: nginx:latest
ports:
- "8080:80"

This will add a simple nginx service to our project. By executing docker-compose up in our project root folder the first time you will use this service will download nginx and try to start running it on port 8080 (be sure this port is available) if this is not the case you could change the port in above example to any port you wish example could be 9000. Opening the application on http://localhost:8080 should show you the nginx welcome page.

Nginx welcome page

AWESOME! we already have our first service up and running! Next lets add our code and site config to the nginx service:

version: "3.4"

services:
web:
image: nginx:latest
ports:
- "8080:80"
working_dir: /code
volumes:
- ./:/code
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf

What we do here is point our local data ./ to our nginx container /code and a local ./docker/nginx/site.conf to the /etc/nginx/conf.d/site.conf this way our docker containers know of the existence of these files. This now is a bit “redundant” since we are mapping our whole root folder into the docker-container. However in a future step we will be ignoring a lot of our project so our docker-container only receives the data it actually needs.

Since we are mapping the ./docker/nginx/site.conf file lets create one:

server {
index index.php index.html;
server_name _;
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
root /code;

location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}

Without going in-depth nginx config is a total new subject. However for us the most important rules in this file is the following:

fastcgi_pass php:9000;

The php:9000 part of this rule is pointing our nginx to our php service. When using docker-compose files all the services that we define. Is callable in other containers by using its name. For example in our current example we could call nginx:80 from within our php container to contact our neighboring service. In real world this is mostly used for services like: databases, caching, websockets or even other microservices within the full suite.

root /code/public;

We are mapping our project to the container in the /code folder. For Symfony the entrypoint for our application lives in public/index.php so to serve content we target nginx to look for files in this specific folder.

Since we are now proxing our requests to the php:9000 internal host lets add the php-fpm service:

version: "3.4"

services:
web:
image: nginx:latest
ports:
- "8080:80"
working_dir: /code
volumes:
- ./:/code
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
php:
image: php:8-fpm
working_dir: /code
volumes:
- ./:/code

And lets look if we can spin up our container by running the docker-compose up command again.

Still had the previous command running use control + c to stop the services.

You should be seeing the beautiful Symfony welcome screen:

Adding a database

Since almost all applications are going to use some kind of database, lets add postgreSQL to our services.

version: "3.4"

services:
web:
image: nginx:latest
ports:
- "8080:80"
working_dir: /code
volumes:
- ./:/code
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
php:
image: php:8-fpm
working_dir: /code
volumes:
- ./:/code

database:
image: postgres:13-alpine
environment:
- POSTGRES_DB=saasapp
- POSTGRES_PASSWORD=!ChangeMe!
- POSTGRES_USER=saasappuser
volumes:
- db_data:/var/lib/postgresql/data:rw

volumes:
db_data:

Here we see 2 new things we have not yet seen before environment and a new global volumes reference. Well in an container sometimes you would want to use the environment section to manipulate behavior or set values that are needed for the service we are using. In the docs of the service you are using is most likely explained what variables are available to set.

The global volumes section is needed in our case to keep data alive when we close control-c the services. What we are telling the database service is that the folder in /var/lib/postgresql/data should be saved in an persistent volume names db_data as the name suggests it persists the data inside the folder on the host computer running the docker agent. If you want to see in the future what volumes are being persisted you can always run docker volume ls in your terminal to view all your persisted folders.

Setting the correct DATABASE_URL in Symfony

Symfony uses the environment key DATABASE_URL to setup the connection between the ORM layer and the database. in our case:

DATABASE_URL="postgresql://saasappuser:!ChangeMe!@database:5432/saasapp?serverVersion=13&charset=utf8"

Restart your docker-compose services docker-compose up and test your database connection by running

docker-compose run php bin/console doctrine:database:create --if-not-exists

and BAM! an error :(

This is caused since the php service does not yet have the php-pgsql extension enabled so php does not know how to talk to our PGSQL database.

But how are we going to add this to are php-fpm docker image? Well lets create our OWN docker image. Thats right YOUR OWN docker-image. Scared yet? Here we go:

Create an new folder phpinto our ./docker directory and create a file inside named Dockerfile so without an extension.

We want to base our logic on another image in our case php:8-fpm so our Dockerfile should start as following:

FROM php:8-fpm

Thats easy isnt it? BUT we need to add postgresSQL so lets add it by using the package manager:

FROM php:8-fpm

RUN apt-get update && apt-get install -y libpq-dev
RUN docker-php-ext-configure pgsql -with-pgsql=/usr/local/pgsql
RUN docker-php-ext-install pdo pdo_pgsql

This is some linux / bash voodoo so for now.

Now lets update our docker-compose config so it knows we want to use our custom image.

version: "3.4"

services:
web:
image: nginx:latest
ports:
- "8080:80"
working_dir: /code
volumes:
- ./:/code
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
php:
build:
context: ./docker/php
working_dir: /code
volumes:
- ./:/code

database:
image: postgres:13-alpine
environment:
- POSTGRES_DB=saasapp
- POSTGRES_PASSWORD=!ChangeMe!
- POSTGRES_USER=saasappuser
volumes:
- db_data:/var/lib/postgresql/data:rw

volumes:
db_data:

so what we did here is we removed the image key from our php service and added the build/context configuration where the service should be looking for its Dockerfile. Restart your docker services docker-compose up and you will see it starts building your new container.

Building phpStep 1/4 : FROM php:8-fpm---> defbff76a240Step 2/4 : RUN apt-get update && apt-get install -y libpq-dev---> Running in 12c2a9cb506a.......

Now that is done. Lets retest our database connection.

docker-compose run php bin/console doctrine:database:create --if-not-exists

NICE! its working :D And we have an docker based Symfony application with a postgreSQL database all running :D

--

--