Home » Laravel » Laravel Development with Docker Tutorial

Laravel Development with Docker Tutorial

Docker is a very popular containerization system. It allows to preparing the environment for your application in a container and deploying it in any operating system with Docker installed. Laravel has a default docker environment, that can be configured using laravel/sail. This package is a wrapper for docker-compose. It can help configure needed containers and their settings. But I prefer creating an environment for my projects manually, without any packages.

It has several benefits. An own configuration may be more flexible, and you can install all tools that you want. In this article we will dive into how to dockerize Laravel application for development.

What Containers You Need

Docker philosophy is to place one process in one container. Well, I will try to stick to it, at least almost. Laravel should have one container per one required service, and another one main container for development. You will be able to connect to this container and install some packages using Composer or run artisan commands. It is important because you can use artisan commands for working with a database only in the container. The containers are required:

  • Nginx – web server, that will make the application available from the browser, handle static resources and bypass PHP queries to PHP-FPM;
  • PHP-FPM – PHP interpreter in the PHP-FPM mode;
  • PostgreSQL – Database for the application;
  • Redis – In the memory storage for queues handling;
  • Main App – You will use this container to interact with the application. Also, this container can handle cron scheduler in the application and queues.

How to Dockertize Laravel for Development

I assume that you have Docker and Docker Compose installed on your computer. If not, install them before reading this article. I will use this directory structure for using Docker with Laravel:

project |--src |--docker |--|--php-fpm |--|--nginx |--|--|--conf.d |--|--main-app |--|--|--supervisor |--docker-compose.yaml |--.env

As you can see, the application sources are separated from the docker configuration. Create a project directory and make it a current directory:

mkdir project cd project

1. Create Docker Compose Config File

First of all, create the docker-compose.yaml config file. In this section we fill first part of the file. In the head, describe network settings and required docker volumes. Volumes are virtual storages, that will be mounted to a selected container into the specified folder. As you know, if you rebuild a container, all changes that have been made manually will be lost. If you want to have persistent data in the container – use volumes. Laravel project requires at least one volume – for Postgresql database. Here is the code:

vi docker-compose.yaml version: "3.9" networks: project-network: volumes: db-data: services:

Specify the config file version at the start of the file. The latest version at this moment is 3.9 and it will work with Docker 19. Next, there are three sections: networks, volumes and services. Networks allow containers to communicate with each other. There are a few network types available, for example: bridge, host, overlay, etc. But by default docker uses the bridge network type. You can just specify the network name in the networks section. For example, project-network. But if you want to use VPN on your computer, the docker network can conflict with it. To resolve this you can specify subnet, like this (optional):

networks: project-network: driver: bridge ipam: config: - subnet: 172.16.57.0/24

After network configuration, we added the db-data volume in the volumes section. It will be used later. All containers configuration will be placed into the services section in Docker Compose for Laravel.

2. Create Environment File

Specify sensitive data for some services. For example, the postgresql container requires database name and password. The best practice is to avoid placing them in the docker-compose.yaml file. This file can be pushed to a public git repository, and sensitive data should not appear there. Docker Compose supports environment files. It will search for .env files in the current directory, so there is no need for additional configuration. Just create the .env file and place these variables into it:

vi .env DB_PASSWORD=12345 DB_NAME=laravel USER_ID=1000

3. Create PostgreSQL container

Let’s start with simple containers and proceed to more complicated ones. We can just use an official PostgreSQL image. This container should use the db-data volume to persist database files, it must be connected to project-network, and must expose port 5433 to the host machine. You can use this port to connect to the database using DataGrip, DBeaver or another tool. Here is the code:

docker-postgresql: restart: unless-stopped image: postgres ports: - "5432:5432" environment: - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_DB=${DB_NAME} volumes: - db-data:/var/lib/postgresql/data networks: - project-network

This config uses environment variables to set database configuration, exposes port 5432 to the host machine, mounts the db-data volume into /var/lib/postgresql/data and connects container to project-network. Also there is one interesting option – restart: unless-stopped. It means that your container will be started automatically even after the reboot until you stop it manually.

2. Create a Redis Container

The container for Redis is also pretty simple:

docker-redis: restart: unless-stopped image: redis ports: - "6379:6379" networks: - project-network

3. Create a PHP-FPM Container

PHP container is more complicated. In this case, the official image will not be enough. Laravel requires several extensions to be installed. Also you need to install extensions for PostgreSQL support. So, we should modify it. I prefer using the official PHP image based on Debian. You may want to use another distro, but all the following examples will work on Debian. First, add these lines to the docker-compose.yaml file:

docker-php-fpm: restart: unless-stopped build: context: ./docker/php-fpm args: - USER_ID=${USER_ID} volumes: - ./src:/var/www networks: - project-network

In this config, we choose to use the context directory with Docker file instead of a container image. Also, we mounted the current directory in the /var/www folder in the container. Then, create a docker directory, and a php-fpm directory inside it. After this, create a Docker file in ./docker/php-fpm/ with this content:

FROM php:8.1-fpm-buster RUN curl -sSLf \ -o /usr/local/bin/install-php-extensions \ https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \ chmod +x /usr/local/bin/install-php-extensions && \ install-php-extensions mbstring pdo pdo_pgsql pgsql zip intl redis RUN cp /usr/local/etc/php/php.ini-development /usr/local/etc/php/php.ini ENV TZ=Europe/Kiev RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone RUN adduser --uid ${USER_ID} --ingroup www-data developer --shell /bin/bash USER developer WORKDIR /var/www

This configuration installs PHP extensions, makes the default development php.ini config used as the main and sets the timezone. You can install php extensions manually, but you can also use this script. It is developed specifically for installing extensions in the official PHP docker images. All PHP files should have the same owner and PHP-FPM process should be run from this user. So, we created a user developer with the same identifier as your system user and made it a the main user for this container. I will explain it in more detail latter.

If you want to add some extra configuration files for PHP, you can place them in ./docker/php-fpm/conf.d and copy in /usr/local/etc/php/conf.d/ using this instruction:

COPY ./conf.d/* /usr/local/etc/php/conf.d/

4. Create a Nginx Container

Nginx container is an entrypoint to the application in the Docker container. The web server will handle static files and redirect all requests for *.php and non existent files to the php-fpm container.

docker-nginx: restart: unless-stopped image: nginx ports: - "8080:80" volumes: - ./src:/var/www - ./docker/nginx/conf.d/:/etc/nginx/conf.d/ networks: - project-network

Here you should mount a current directory with the application sources in the /var/www directory in the container just as in the php-fpm container. Next, expose port 80 to the host machine. In this example, I use the port 8080 in the host. Nginx should have a configuration file for your site. Create a directory ./docker/nginx/conf.d/ and add a configuration file application.conf to it. This directory is mounted to /etc/nginx/conf.d/ in the code above.

vi ./docker/nginx/conf.d/application.conf server { listen 80; server_name application.local !default; root /var/www/public/; client_max_body_size 64M; index index.html index.htm index.php; charset utf-8; location / { try_files $uri $uri/ /index.php?$query_string; } error_page 404 /index.php; location ~ \.php$ { fastcgi_pass docker-php-fpm:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; include fastcgi_params; } }

This config is very close to default. You can change application domain from application.local to other that you want. The section “location /” configures Nginx to redirect all non existent files to index.php; then configure the path to the PHP-FPM container and some parameters in the php section.

5. Create the Main App Container

The main-app container is the most complicated. Here, we need to create a container from the Dockerfile, because we need to install PHP extensions, install Composer and any other tools which you want to work. First add this section to docker-compose.yaml:

main-app: restart: unless-stopped build: context: ./docker/main-app args: - USER_ID=${USER_ID} volumes: - ./src/:/var/www networks: - project-network depends_on: - docker-postgresql

This container will be based on the CLI version of official PHP images. Supervisor will be the main process here. It will run a schedule worker and queue workers. You can read more about Supervisor here. Create the Docker file in docker/main-app with this content:

vi docker/main-app/Dockerfile FROM php:8.1-cli-buster ARG USER_ID RUN curl -sSLf \ -o /usr/local/bin/install-php-extensions \ https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \ chmod +x /usr/local/bin/install-php-extensions && \ install-php-extensions mbstring pdo pdo_pgsql pgsql zip intl redis RUN cp /usr/local/etc/php/php.ini-development /usr/local/etc/php/php.ini RUN apt-get update && apt-get install -y supervisor COPY supervisor/ /etc/supervisor/conf.d RUN chmod -R 777 /tmp /var/tmp RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer RUN sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/' /etc/ssl/openssl.cnf RUN adduser --uid ${USER_ID} --ingroup www-data developer --shell /bin/bash WORKDIR /var/www CMD ["supervisord", "-n"]

Laravel has helper commands, that allow creating models, migrations, controllers and many other entities. If you want to use these artisan commands in the container and to be able to edit created files in your IDE, your system user identifier should be the same as the user in the container. That is why the user ID was set in the Docker environment file and the user was created in the container.

Create the docker/main-app/supervisor directory and the queue.conf file inside it with the following content:

vi docker/main-app/supervisor/queue.conf [program:queue-worker-run] process_name=%(program_name)s_%(process_num)02d command= php artisan queue:work --timeout 0 directory = /var/www autostart=true autorestart=true numprocs=1 user=developer stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0

In this config, we run a single instance of the command queue:work. It will handle the default queue and have no timeout. The log file will be redirected to the docker-compose stdout. Next, create scheduler.conf with the following content:

vi docker/main-app/supervisor/scheduler.conf [program:schedule-worker-run] process_name=%(program_name)s_%(process_num)02d command= php artisan schedule:work directory = /var/www autostart=true autorestart=true numprocs=1 user=developer stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0

This section does the same configuration for the schedule:work command. It will handle Laravel cron jobs, configured in app/Console/Kernel.php. Now, you are ready to run the application.

6. Install application sources

Lets have a look at how to install Laravel in the Docker container. Change the current directory to ./src and place application sources into it. In this article, I will deploy this default Laravel boilerplate. You can clone it using git:

git clone https://github.com/laravel/laravel.git ./

Ensure, that all source files are owned by your current user. In this example, it is user sergiy. You can change the owner of all users and directories using the following command:

sudo chown sergiy:sergiy -R ./

It’s time to run containers and connect to the main-app container. Use this command to build an environment:

docker-compose up --build

You can see errors from Supervisor, that it can run a queue and scheduler. It is okay because the application is not installed at the point. If you configure to run a queue worker as the main process, you will not be able to log into the container, because the container will exit after the main process has crashed. But in this case, Supervisor is working, and you can interact with the container. Log into container using this command:

docker-compose exec main-app -u developer bash

And run this command to install all composer requirements:

composer install

7. Configure Laravel .env File

Copy the .env.example file to .env:

cp .env.example .env

Create an encryption key using this command:

php artisan key:generate

Open the .env file and change database settings to this:

DB_HOST=docker-postgresql DB_DATABASE=application_db DB_USERNAME=postgres DB_PASSWORD=12345

Change Redis settings to this:

REDIS_HOST=docker-redis REDIS_PASSWORD=null REDIS_PORT=6379

Change APP_URL to http://application.local

APP_URL=http://application.local

Re-run Docker Compose containers and you will see that the queue worker and scheduler have been run successfully:

docker-compose down docker-compose up

8. Run Application

Use any database client, for example DBeaver to create a database for the application. Docker exposes PostgreSQL port (5432) to your machine, so you can connect to it in the same way as to local PostgreSQL. In this example the database name is application_db. Then, open the /etc/hosts file and add this line to the end of the file:

127.0.0.1 application.local

After this, run the following command to migrate all application migrations to the container:

php artisan migrate

At this moment you can open http://application.local:8080 in your browser and check that the application is working:

You can create scheduled commands or queue jobs to ensure that everything works as exprected. Now, you have a ready environment for Laravel development with Docker. Here is full contents of docker-compose.yaml if you want to copy an entire file:

version: "3.9" networks: project-network: volumes: db-data: services: docker-postgresql: restart: unless-stopped image: postgres ports: - "5432:5432" environment: - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_DB=${DB_NAME} volumes: - db-data:/var/lib/postgresql/data networks: - project-network docker-redis: restart: unless-stopped image: redis ports: - "6379:6379" networks: - project-network docker-php-fpm: restart: unless-stopped build: context: ./docker/php-fpm args: - USER_ID=${USER_ID} volumes: - ./src/:/var/www networks: - project-network docker-nginx: restart: unless-stopped image: nginx ports: - "8080:80" volumes: - ./src:/var/www - ./docker/nginx/conf.d/:/etc/nginx/conf.d/ networks: - project-network main-app: restart: unless-stopped build: context: ./docker/main-app args: - USER_ID=${USER_ID} volumes: - ./src:/var/www networks: - project-network depends_on: - docker-postgresql

Wrapping Up

In this article we have explained how to use Docker for Laravel development in the local environment. This configuration can be pushed to GitHub and deployed on any other machine. Or you can place a few projects in the ./src folder and use it only locally. What technologies do you use for the local development? Do you use Docker?

2 thoughts on “Laravel Development with Docker Tutorial”

    • The docker-php-fpm container is used in Nginx container to handle PHP requests from users while the main-app handles queues, scheduler and PHP in the command line. Here:


      location ~ \.php$ {
      fastcgi_pass docker-php-fpm:9000;
      fastcgi_index index.php;
      fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
      include fastcgi_params;
      }

      Reply

Leave a Comment