How setup and run Symfony with Docker.
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.
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 php
into 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