Laravel & PHP Dockerized Project
An example of a more complex set up
Introduction
In this section, the instructor guide us through setting up a Docker infrastructure to be able to run a web application using Laravel and PHP. The choose of building a Laravel and PHP web application infrastructure is only a pretext to teach us some Docker features and concepts. The use of Laravel and PHP also requires a complex infrastructure that can be very troublesome to set up. However, this section is a demonstration of how Docker can simplify all the process.
Nginx Container: Host the web application and forward the web resquest to the PHP interpreter.
Nginx Server Container
The first step was to prepare the configurations for the nginx server in the docker-compose.yaml
file.
We named server the service associated to the nginx container. The container will be build based on the nginx:stable-alpine
official Docker image. The port 80 is exposed internally and we bind it to port 8000 in our host machine. The volume created is to pass our own nginx.conf
file to the default.conf
file inside the container. The location of the nginx.conf file inside the Docker container is located in /etc/nginx/conf.d/default.conf
as specified in the Docker documentation. The ro
tag is to grant read only permission so the container could not make any change to our config file.
Official Nginx Docker Image -> here
We created the appropriated folder tree on our host machine. The nginx.conf
file has been attached to the folder project. We did not have to create it from scratch.
Php Interpreter Container
Creation of a custom Php image
A custom image is needed for the Php container since some dependencies we need to build our app are missing from the official Php Docker image. We created a php-dockerfile. Our custom image will be based on the 7.4-fpm-alpine php official Docker image, which is a light weight version of php. The php.dockerfil
e created is located on our host machine in the dockerfiles
folder.
Modification of the docker-compose.yaml
Based on that custom Dockerfile, we can set up the php service in the docker-compose.yaml
file. We created a bind mount volume since the php container needs to have access to our source code. We created a /src
folder on our local host machine that will contain our source code. The /src
folder will be bind to the /var/www/html
folder in the php docker container.
Modification of the nginx.conf file
We have to make a slight modification in the nginx.conf
file for the php container to communicate with the nginx service. The Dockerfile of the official PHP Docker image indicates that port 9000 is exposed internally. Since every services include in a same Docker compose file are in the same network, they can communicate with each others. We will leveraged this fact and make our nginx server communicate directly with the php container on port 9000 instead of having an intermediate through localhost on port 3000.
Therefore we can get rid of this instruction in the Dockerfile that bind port 3000 on our local host to the Php container on port 9000.
We just have to change the nginx.conf file for the nginx server to communicate directly with the Php container on port 9000.
MySQL Service Container
We created the MySQL container based on the official Docker image (version 5.7). Some environment variables need to be specified for the MySQL service. We created an env/mysql.env
file on our local host and specified the path in the docker-compose.yaml
fie.
The mysql.env
file contains the following environment variables (see below). The name of the environment variables are specified in the official documentation here.
I updated the docker-compose.yaml file with the mysql service.
Composer Container
In this section, we add the composer container. This container will be based on the latest official Composer Docker image. However, we created a container.dockerfile
file because we wanted to add an entrypoint.
Then, we add the composer container to the docker-compose.yaml
file and we bind the /src
folder located on our host machine to the container working directory. This way the composer utility will have access to the source code and any changes will be reflected on the source code located on our host machine.
Here is shown only the composer service part of the docker-composer.yaml
file.
Creating a Laravel Application
Now, that we configured our Composer container, we can create a Laravel application. Here the docker-compose run
command will be used to only start the composer service.
The command below will create a Laravel project inside the container in the current directory. Since the working directory is /var/www/html
, the Composer utility will create the application within that folder, and all the files will be reflected in the /src
directory on our host machine.
Once the above command was run, we could observe in the /src
folder the big hierarchy of files that compose our Laravel application.
Connecting the MySQL database
The .env
file located in the /src
folder contains environment variables such as the MySQL Database configuration. The value are set by default by Laravel. However, we had to change the value of these environment variables for those specified in the mysql.env
file we previously created. The value DB_HOST points towards the mysql service that is configured in the docker-compose.yaml
file. Since the Laravel application and the MySQL containers are running in the same network, Docker will be able to resolve the mysql name to an internal IP address for both services to be able to talk to each others.
Adding a bind volumes to the Nginx container
The nginx web server is the entry point of our application and its role is to forward the web requests to the PHP interpreter. To accomplish this task, the web server needs to be able to communicate with the web application source code.
That's why adding a new bind volumes to the nginx web server is needed.
Starting the server, php and mysql services only
With the docker-compose up command, we can specify only specific services we want to start.
For example, if we want to start only the nginx server, the php interpreter and the mysql services without starting the composer service, we can run the following command:
To avoid listing all the services we want to start, we can also specify to Docker compose that one service depends on others. For example, since, the nginx server depends on both php and mysql services, Docker will start automatically the php and mysql services before starting the nginx service.
To start all three services, we only have to specify to the docker-compose up
command to start the server.
Forcing to rebuild the images
When starting our services, docker-compose won't rebuild the images that have already been built. Therefore, if we make any changes to the Dockerfile and we want the changes to be reflected in our infrastructure, we need to specify to Docker to rebuild our images based on the Dockerfiles. This can be done with the flag --build.
If no change has been made, Docker will used the images that are stored in its cache.
Troubleshoot
At starting the application, I had two errors. The first error I got was associated with the PHP version upon which I built my PHP images container. PHP was not able to parse a character in the source code. What I did is to change the version of PHP used in the Dockerfile.
The second error I got was related to permissions. I had this error where it was impossible to write in the laravel.log file. In both my composer.dockerfile
and php.dockerfile
, I add an user named "laravel" which will be the owner of the source code when creating the project with Composer. This step ensure that the laravel user exists on both container and has the correct permission to make changes in the source code.
After fixing these errors, I could get my web application work correctly on localhost:8000.
Artisan container
The Artisan container will be used to run some Laravel commands. For example, the Artisan utility will be used to populate the database.
The artisan service is running upon PHP, so the image of the service will be based on the PHP custom images we created earlier. We created a volume because artisan will need to have access to our source code. Then, we specified an ENTRYPOINT
to the docker-compose.yaml
file under the artisan service. Since the artisan service is an utility, we might need to specify an entry point because we will ask this service to run some commands. The entrypoint fields in the docker-compose.yaml
file allows to overwrite the ENTRYPOINT
specified in the Dockerfile or add an entrypoint to the service if not specified in the Dockerfile.
Then, we can populate our database with some initial data by running this command:
To populate my database with data, I had to make sure that my DB environment variables were set with the proper value, since I had to recreate the project due to the errors encountered when starting the Laravel application. At the beggining, the Artisan utility could not join to the database, cause I forgot to change the value of these variables in the /src/.env
file:
NPM container
To configure the NPM service, we reinvest our knowledge that it is possible to overwrite the default configuration specified in the Dockerfile or in the default image by adding specific configurations to the docker-compose.yaml
.
In this section, we configured the npm service directly in the docker-compose.yaml
file instead of creating a new Dockerfile. We specified the working directory with the working_dir
field and added the npm
entry point to be able to run some npm
command. As with others services, we created a volume for the npm service to have access to our source code.
Bind Mounts and COPY
The instructor reminds us that the creation of bind mounts pointing to folders on our localhost machine is a good approach during the development stage, but not when it's time to deploy the application. Indeed, the advantages of Docker is that everything we need to run the application should be located in a single and isolated environment. Therefore, if we want to share our configurations files with someone else, it is obvious that he won't have the files we host on our localhost machine.
To do that, we can create a Nginx Docker image containing a snapshot of the configurations and source code of our application.
Firstly, we created the nginx.dockerfile file
. During the creation of the image, the nginx configuration file and the source code located on our host machine will be copied inside the container.
Then, we had to make modifications to the docker-compose.yaml
file for the nginx service to be build upon our Dockerfile.
What is important to notice here is that the build context of the server is the root directory and not the dockerfiles directory as we were used to instructed Docker. This is necessary because when creating the nginx image, some instructions inside the Dockerfile referrers to folder and files (the configuration and source code that are meant to be copied) that are located outside the dockerfiles
folder. Thus, specifying the context of the build to the folder dockerfiles
won't allows the image to be built correctly.
The same principle apply for the php service. We will want to take a snapshot of our source code inside the php container. For that we will add this line to the php.dockerfile
file. This line will copy the content of the /src
directory located on our hostmachine in the working directory of the php container.
We also make changes to the php service section of the docker-compose.yaml
file. Again, the context can't be the dockerfiles folder since instructions in the php.dockerfile
referers to folders outside the dockerfiles folder.
We can then rebuild and start the new images that will contain a snapshot of our source code and configuration files.
However, when inspecting the web application on localhost:8000, we got an error.
This error was due to the fact that the laravel user, does not have READ and WRITE access to the /var/www/html
folder where he is trying to write into. We need to grant this user the proper access rights by modifying the the php.dockerfile
.
Last modification to the Artisan service
Since the Artisan service is built on upon the php services, we also need to change the build context and dockerfile fields. Otherwise, we won't be able to run the artisan commands.
We can then run the artisan command again which aim to build the project.
Last updated