Setting up Laravel Development Environment with Docker
Introduction to building a basic Laravel development environment using Docker
In the past, developing Laravel projects locally mostly involved installing the required services directly on the machine.
Later, the recommended development environment by the official documentation, Homestead
, became a popular choice.
Subsequently, Laradock
emerged as a containerized PHP development environment, pre-configured with commonly used containers.
Laradock can be considered the harbinger of Laravel’s container era.
In the latest version of Laravel 8
, container environment is directly integrated.
Laravel Sail
is a lightweight CLI that provides a container for Laravel projects built with PHP, MySQL, and Redis.
It allows usage of the container without requiring prior Docker experience.
But why bother learning Docker and setting up the environment on your own when Laravel Sail
is available?
Well, because it didn’t exist initially.
While local development may not encounter situations requiring configuration file adjustments, you may face scenarios where services you need are not provided by Laravel Sail
.
If you want to switch your online environment to use Docker or need to know how to build an environment with Docker, understanding Docker and configuring related services becomes necessary.
After a lengthy introduction, let’s get to the main content.
Setting up Laravel Project Environment
Use docker-compose
to construct the Laravel project environment. A Laravel project typically requires the following services:
- nginx
- php
- redis
- PostgreSQL
For the database, we’ll use PostgreSQL, but if you prefer MySQL, the configuration is also provided in the code.
Choose one when starting!
1. docker-compose.yml
Start by creating a project folder, let’s call it docker_env
for clarity. Inside the docker_env
folder, create a file named docker-compose.yml
to serve as the configuration file for Docker-compose services.
Complete Code
# docker-compose.yml
version: '3'
networks:
server:
data:
services:
nginx:
container_name: docker_env_nginx
image: nginx:1.18 # stable version
networks:
- server
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d/:/etc/nginx/conf.d/
- ./nginx/ssl/:/ssl/
- <local project path>:/var/www/html/<project name>
restart: always
php:
container_name: docker_env_php
build: ./php/
expose:
- 9000
networks:
- server
- data
volumes:
- /trp/web/home/:/var/trp/
restart: always
redis:
container_name: docker_env_redis
image: redis:6.0 # stable version
ports:
- "6379:6379"
networks:
- data
restart: always
postgre:
container_name: docker_env_postgre
ports:
- "5432:5432"
image: "postgres:12.6"
volumes:
- /trp/database/dev/database_data:/var/lib/postgresql/data # persist data even if container shuts down
networks:
- data
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
restart: always
mysql:
container_name: docker_env_mysql
ports:
- "3306:3306"
image: mysql:5.7.23
volumes:
- /var/lib/mysql
networks:
- data
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
Section Explanation
Here, we will explain the contents of the services
section and complete the necessary configurations.
If you simply copy and paste the above, the services are likely to fail.
nginx
Uses nginx as the web server for our application.
nginx:
container_name: docker_env_nginx
image: nginx:1.18 # stable version
networks:
- server
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d/:/etc/nginx/conf.d/
- ./nginx/ssl/:/ssl/
- <local project path>:/var/www/html/<project name>
restart: always
ports
defines the mapping of ports between the host machine and the nginx container.
In the provided code, it may not be immediately apparent.
To clarify, using 8080:80
means mapping the host’s 8080 port to the nginx container’s 80 port.
volumes
block indicates files to be mounted into the Docker container.
The third line mounts the specified Laravel project into the container’s designated directory.
If you don’t have a Laravel project yet, create one.
The second line mounts SSL certificates if you want to use HTTPS locally.
You can generate a self-signed certificate and place it in the docker_env/nginx/ssl/
directory.
While this will display as insecure, it will still be an HTTPS URL.
The first line mounts the nginx
folder in the same level as the docker-compose.yml
into /etc/nginx/conf.d/
, which is the directory used by the nginx service for configuration files.
Therefore, we need to create an nginx config file in the docker_env/nginx/
folder.
# dev.project.com.conf
server {
listen 80;
listen [::]:80;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
root /var/www/html/<laravel project name>/public;
index index.php index.html index.htm;
ssl_certificate /ssl/ssl.crt;
ssl_certificate_key /ssl/ssl.key;
server_name dev.project.com;
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_pass docker_env_php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
In the above config, the part listening on port 80 immediately redirects to port 443 (HTTPS).
The root
specifies the location of the project folder, and public
is the entry point for Laravel projects.
The SSL certificate paths correspond to the mounted paths specified in the volumes
section.
The fastcgi_pass
setting specifies the container_name and port of the container with PHP-FPM installed.
Note that the names and ports used are not fixed!
php
php:
container_name: docker_env_php
build: ./php/
expose:
- 9000
networks:
- server
- data
volumes:
- /trp/web/home/:/var/trp/
restart: always
As the PHP container doesn’t need an external port, expose
is used to make the container’s 9000 port accessible within the Docker network.
Volume mounting is similar to the nginx service.
To use Laravel, some additional PHP extensions need to be installed. This is done with the build
command, which rebuilds our own Docker image.
It installs necessary Laravel PHP extensions, and the database extension depends on the chosen database.
The example below demonstrates the setup for PostgreSQL.
Here, we also show how to install Composer. Uncomment the relevant lines if needed.
However, since PHP doesn’t require pre-compilation, you can make changes directly in the files mounted by the volumes, enabling internal program modifications without installing Composer inside the container.
Unless there’s a specific reason to run commands inside the container, it’s not recommended to install Composer in the container.
Create a php
folder in the docker_env
directory and add a file named Dockerfile
with the following content:
# Dockerfile
FROM php:7.4-fpm
WORKDIR /var
RUN apt-get update && apt-get install -y libpq-dev libpng-dev libzip-dev zip
RUN docker-php-ext-install pgsql pdo_pgsql gd zip\
&& docker-php-ext-enable opcache
# To use mysql, install pdo pdo_mysql instead of pgsql pdo_pgsql
# Install composer Latest
# RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
# && php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
# && php composer-setup.php \
# && php -r "unlink('composer-setup.php');" \
# && mv composer.phar /usr/local/bin/composer
database
postgre:
container_name: docker_env_postgre
ports:
- "5432:5432"
image: "postgres:12.6"
volumes:
- /trp/database/dev/database_data:/var/lib/postgresql/data # persist data even if container shuts down
networks:
- data
environment:
POSTGRES_DB: ${DB_NAME}
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
restart: always
mysql:
container_name: docker_env_mysql
ports:
- "3306:3306"
image: mysql:5.7.23
volumes:
- /var/lib/mysql
networks:
- data
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
The volumes
setting for the database mainly focuses on data persistence.
Even if the container is shut down, the data is retained. Database-related environment parameters are set in the environment
section.
As these parameters may include sensitive information, you can create an .env
file in the docker_env
folder to store them without version control:
# .env
DB_USER=db_user
DB_PASSWORD=db_password
DB_NAME=my_database
The keys on the left side can be called in the docker-compose.yml
file using ${key}
, allowing retrieval of the values set on the left side.
Starting the Containers
After these steps, with Docker service running, navigate to the docker_env
folder and run the command:
docker-compose up -d
If everything is correct, the containers will start in the background.
This completes the goal of “Setting up Laravel Environment with Docker!”
To check the container status, you can use the command:
docker ps -a
If I were personally writing a new docker-compose.yml
configuration, I would first use:
docker-compose up
to display logs in real-time. This makes debugging much easier.
Common issues might include missing volume directories like nginx config, SSL, or the Laravel project.
Laravel .env Configuration
After successfully starting the Docker environment, configure the Laravel project’s environment file.
Open the .env
file in the root directory and modify its content.
In the example below, PostgreSQL is used for the database configuration, and it aligns with the settings in the docker-compose.yml
file:
# .env
DB_CONNECTION=pgsql
DB_HOST=docker_env_postgre
DB_PORT=5432
DB_DATABASE=my_database
DB_USERNAME=db_user
DB_PASSWORD=db_password
Now, Laravel can access the database container through the internal Docker network.
Setting Up Local Test URL
With Docker setting up Laravel development environment - Done
Connecting Laravel to Docker Database - Done
Now, we need to make sure that we can open the local Laravel project in the browser.
Open your terminal, whether it’s bash or zsh:
sudo vim /etc/hosts
Add the following line in /etc/hosts
:
127.0.0.1 dev.project.com
This line directs requests for dev.project.com
to the localhost IP.
When entering dev.project.com
in the browser’s address bar, the request will be sent to the localhost.
But how does this make the Laravel project visible? This is because in the earlier nginx config settings, a server_name
was defined:
server_name dev.project.com;
So, when the Docker nginx service, listening on ports 80 and 443, detects an incoming request that matches the server_name
configuration, it processes the request.
If you have another nginx config file with default_server
appended to the listen port, like this:
listen 80 default_server;
It means that if no other nginx config rules match, nginx will use this configuration. If all config files don’t
set default_server
, then the first config file sorted alphabetically will be used.
With this setup, you should be able to access the Laravel project’s interface.
Running Laravel Commands
If you want to run Laravel commands related to the database, such as:
php artisan migrate
You first need to enter the PHP container:
docker exec -ti docker_env_php bash
Inside the container, navigate to the project directory and run the command.
If successful, you’ll see the command executed correctly.
If you run the migrate command without entering the container, you’ll encounter an error message like:
SQLSTATE[08006] [7] could not translate host name "docker_env_postgre" to address: nodename nor servname provided, or not known
This is because the program doesn’t recognize the host “docker_env_postgre.”
If we modify the .env file’s DB_HOST to the real location using the docker inspect
command, it would work, but this is not a sustainable solution.
If you find it cumbersome to enter the container or want to run commands directly in the local project folder, what should you do? Modify the etc/hosts
file again by adding:
127.0.0.1 docker_env_postgre
Now you’re done! Running migrate in the local project folder will direct it to the local container.
The above is the entire content of this guide on “Building a Laravel Development Environment with Docker.”
It took several hours to complete this piece, and it’s a culmination of a lot of server knowledge.
If you’re not familiar with these concepts, it’s recommended to understand them before proceeding.
Due to space limitations, I could only provide a brief explanation of the configurations and skip many details and practices.
There are also many aspects not covered, such as Docker network segmentation and usage.