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.
Table of Contents
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?
i dont see that you did not use docker-php-fpm ? what is purporse that container ?
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;
}