How To Setup A PHP Laravel Development Container With Docker

How To Setup A PHP Laravel Development Container With Docker

Recently, I've learned to use Docker for my project development environment setup.

A Docker container allows you to contain all the necessary development software and environment needs into one complete package for easy distribution.

By using a development container, anyone in your development team will be able to clone and have the exact same setup ready for development with just one command.

In this article, we will walk through the steps in creating a Docker container for Laravel Development that will include an NGINX webserver, PHP with extra extensions, as well as MySQL database.

In addition, we will throw in adminer for easy database management.

πŸ›  Prerequisites

Docker

You need to install Docker on your machine. If you are new to Docker, I recommend you to download Docker Desktop from the following site:

Git

Make sure your machine has Git installed.

Project Folder

Before we begin, please create a new folder for your project. All files will be created in this folder.

We will call this folder my-project.

1. Clone or setup a Laravel project

Make sure you are in the my-project directory.

~$ cd my-project

Clone an existing Laravel codebase or set up a new Laravel project.

my-project$ git clone https://github.com/laravel/laravel.git laravel

Note that it clones the project files into a folder named laravel.

2. Install Composer

Composer is usually required when developing with Laravel.

Instead of forcing your developers to install Composer globally on their machine, we can use Docker’s composer image to mount the directories that are needed by your Laravel project.

Move into the ./laravel directory and run the command:

my-project$ cd laravel

laravel$ docker run --rm -v $PWD:/app composer install

The -v and --rm flags are used to create a short-lived container that will be bind-mounted to the /laravel directory before being removed. This ensures that the /vendor folder that Composer creates inside the container is copied to your current directory.

3. Customize a new PHP Docker image

To be able to run Laravel and Composer, and to connect with MySQL database, we will need to install a few PHP extensions.

We will refer to the official Laravel's requirements here:

The official Docker PHP image provides only the bare minimum for most PHP applications. Therefore, to fulfil our requirements, we will have to build our own Docker image.

Move back to my-project folder and create a new folder php-fpm:

laravel$ cd ..

my-project$ mkdir php-fpm

Create a new file with the filename Dockerfile inside the new ./php-fpm folder:

# Use the PHP-FPM 7.4 image
FROM php:7.4-fpm

# Install the necessary software and libraries
RUN apt-get update && apt-get install -y \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libzip-dev \
    git \
    zip \
    unzip \
    curl

# Install the required PHP extensions
RUN docker-php-ext-install bcmath mysqli pdo_mysql zip
RUN docker-php-ext-configure gd --with-freetype --with-jpeg
RUN docker-php-ext-install -j$(nproc) gd

# Install xdebug
RUN pecl install xdebug \
    && docker-php-ext-enable xdebug

# Install composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

When saving the file, make sure the Dockerfile filename does not contain any file extension like .txt.

4. Setup NGINX site configuration

In order for our Laravel application to be served by NGINX webserver, we need to create an NGINX site .conf file.

In \my-project folder, create a new folder nginx and a subdirectory conf.d within the new folder:

my-project$ mkdir -p nginx/conf.d

The -p flag will allow the creation of subdirectories when the parent directory is not present.

Create a new configuration file site.conf in ./nginx/conf.d directory:

server {
    listen      80;
    server_name localhost;
    root        /var/www/laravel/public;

    add_header  X-Frame-Options "SAMEORIGIN";
    add_header  X-XSS-Protection "1; mode=block";
    add_header  X-Content-Type-Options "nosniff";

    index       index.php;

    charset     utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    # pass the PHP scripts to FastCGI server listening on [server]:9000
    location ~ \.php$ {
        fastcgi_pass    app:9000;
        fastcgi_param   SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include         fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

The above configuration is based upon the recommended site configuration by Laravel here:

Please note the following:

i) The root /var/www/laravel/public; is where we will store our Laravel application codebase.

ii) fastcgi_pass app:9000; is the Docker network DNS name for the PHP-FPM container, which we will see in the next step.

5. Create a docker-compose.yml configuration

Instead of running each container service one by one, a docker-compose.yml file will define each container image and setup details that can be called by Docker Compose with a single command.

Let's start by defining the services we need. Create a new docker-compose.yml configuration file in /my-project folder:

version: "3.7"

services: 
  web:
    image: nginx:latest
    container_name: webserver

  app:
    build: 
      context: ./php-fpm
      dockerfile: Dockerfile
    image: php-fpm-laravel
    container_name: app
    working_dir: /var/www/laravel

  db:
    image: mysql:8.0
    container_name: db

  adminer:
    image: adminer
    container_name: adminer

Here, we have defined four services in our configuration. As you can guess from the image: field, each service will serve a different purpose.

This is the recommended practice when setting up container services: Each container should do one thing and do it well.

A few things to take note of the configuration settings:

i) Under the app service, we are building a custom PHP Docker image using the build commands in ./php-fpm/Dockerfile defined previously.

ii) container_name is important for containers to identify each other within a Docker network.

iii) The working_dir: /var/www/laravel under app service will be the location where the Laravel application codebase is stored in the container. This is important when we need to execute PHP Artisan & Composer commands in the container later.

6. Mapping Access Port

To access the Laravel app & adminer from our localhost machine, we need to define each port that maps to the web & adminer service container.

i) In the docker-compose.yml file, add the following port configuration under the web service:

...
services: 
  web:
    image: nginx:latest
    container_name: webserver
    ports:
      - "8000:80"
...

This means that we will access our Laravel app via localhost:8000

ii) Add the following port configuration under the adminer service:

...
  adminer:
    image: nginx:latest
    container_name: webserver
    ports:
      - "8888:8080"

This will allow us to manage our MySQL database using adminer from localhost:8888

7. Set a MySQL Database & Password

Let's set a database and password for MySQL database container.

i) In docker-compose.yml file, add the following environment: configuration under the db service:

...
db:
    image: mysql:8.0
    container_name: db
    environment: 
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: secret
...

ii) Make a copy of ./laravel/.env.example file and name the new file ./laravel/.env. Then, open the newly copied file ./laravel/.env and edit it with the following MySQL Database information:

...
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=secret
...

Note that the DB_HOST=db must be equivalent to the db service container_name: db from docker-compose.yml file.

❗️It's not a good practice to use the root account for database access, but it's acceptable for development purposes in a container deployed on your local machine.

8. Setting up Docker Volumes

At this point, your project directory structure should look like this:

my-project
    \_ laravel
        \_ app
        \_ ...
        \_ public
        \_ vendor
        \_ .env
        \_ ...
    \_ nginx
        \_ conf.d
            \_ site.conf
    \_ php-fpm
        \_ Dockerfile
    \_ docker-compose.yml

Next, we are going to create Docker Volumes for several purposes:

i) First, the Laravel application codebase in our local machine will be mounted to the containers so that any code changes made to our local files will be reflected in the containers without having to reload our containers.

This will speed up the work of your development process.

ii) Second, to mount the NGINX site configuration from Step 4 before that will override the default site configuration in the NGINX container.

This exposes the site configurations to your development team so that everyone understands how the webserver is being setup.

This also allows us to change our site configuration conveniently at any time without having to do it in the container.

iii) Thirdly, to persist the data stored in MySQL Database so that all data created inside the container database is retained even when the containers are being brought down.

This is useful when we need to develop a huge feature that spans several days and needed the data to persists for development continuation purposes.

Here's the complete docker-compose.yml configuration after adding the volume mounts for each container:

version: "3.7"

services: 
  web:
    image: nginx:latest
    container_name: webserver
    ports:
      - "8000:80"
    volumes: 
      - ./laravel:/var/www/laravel
      - ./nginx/conf.d/:/etc/nginx/conf.d/

  app:
    build: 
      context: ./php-fpm
      dockerfile: Dockerfile
    image: php-fpm-laravel
    container_name: app
    working_dir: /var/www/laravel
    volumes: 
      - ./laravel:/var/www/laravel

  db:
    image: mysql:8.0
    container_name: db
    environment: 
      MYSQL_DATABASE: laravel
      MYSQL_ROOT_PASSWORD: secret
    volumes:
      - laravel-mysql-data:/var/lib/mysql

  adminer:
    image: adminer
    container_name: adminer
    restart: always
    ports:
      - "8888:8080"

volumes: 
  laravel-mysql-data:

Note the last line laravel-mysql-data: is where the database data will be stored persistently in a Docker volume.

9. Run Docker Compose

All the necessary files and configuration is in place. Now let's spin up our containers using Docker Compose.

Make sure you are in the my-project directory where the docker-compose.yml file is located and run the following command:

my-project$ docker-compose up -d

The -d flag is to instruct Docker to run the containers in detached mode.

Please be patient as the build process will take some time to complete. Once it's completed, you will see the following response in your terminal:

Creating webserver ... done
Creating db        ... done
Creating adminer   ... done
Creating app       ... done
my-project$

If you have installed Docker Desktop, open the Dashboard to view your new containers:

Screenshot 2020-12-06 at 5.08.44 PM.png

10. Execute PHP Artisan & Composer commands

Normally, you do not include the .env file and ./vendor/ folder in your code repository. Therefore, you need to include in your repository README file with the following instructions for your development team on how to run php artisan & composer commands in the containers.

So, before we can access the Laravel application from the browser, we need to run a few commands to set up the necessary data required by Laravel.

i) First, let's generate the application encryption key:

$ docker exec app php artisan key:generate

Let's break down the command:

  • docker exec: the Docker command to execute commands in a container
  • app: the service container_name defined previously in docker-compose.yml file
  • php artisan key:generate: the actual command you intended to run in the container

You will see Application key set successfully. returned for the above successful command.

ii) Next, let's install all composer's dependencies:

$ docker exec app composer install

iii) To clean up composer cache:

$ docker exec app composer dump-autoload

Now, you can access your application via localhost:8000 on a web browser where you will see your Laravel application being loaded.

Screenshot 2020-12-06 at 8.41.16 PM.png

11. (Optional) Database migration and adminer access

Next, I'll show you how to install new composer dependencies, run database migration, and view them in adminer.

Let's assume your application needs to use the Laravel Passport to handle authentication. You can install the dependencies with the following command:

$ docker exec app composer require laravel/passport

To create the tables necessary for Laravel Passport, you may run the following command:

$ docker exec app php artisan migrate

And lastly, to create the encryption keys needed to generate secure access tokens in Laravel Passport:

$ docker exec app php artisan passport:install

Now, let's inspect the tables and data in MySQL database via adminer by going to localhost:8888 using your web browser:

Screenshot 2020-12-06 at 5.58.08 PM.png

Login with the following credentials:

  • Server: db
  • Username: root
  • Password: secret
  • Database: laravel

You should see the tables and their data created from the migration and installation processes earlier:

Screenshot 2020-12-06 at 6.25.32 PM.png

12. Shutting down containers

Finally, we can shut down our containers once we are done working on our project.

$ docker-compose down

You will see the following response messages in your terminal after the process has completed:

Stopping webserver ... done
Stopping app       ... done
Stopping adminer   ... done
Stopping db        ... done
Removing webserver ... done
Removing app       ... done
Removing adminer   ... done
Removing db        ... done
Removing network my-project_default
my-project$

Alternatively, take a look at the Docker Desktop Dashboard. You will find that your project containers are gone from the list.


🏁 Summary

πŸŽ‰ That's all for today! We have come to an end to the walkthrough of setting up a Laravel Development Container using Docker.

With this method, we can say goodbye to setting up a different development environment manually on your machine for every project. Your development team will no longer have to spend the whole day setting up their machine before they can start working on the project. Not to mention having to make sure all software and environments must match the project system requirements.

Hopefully, by learning how to use containers as a development environment, you will no longer hear your developers saying "It works on my machine!". πŸ˜‰

πŸ™ Thank you for reading! If you find this article useful, please share it with your followers.

πŸ’¬ If you have any questions, feel free to leave a comment below.

πŸ“š References

Β 
Share this