Byte Ebi's Logo

Byte Ebi 🍀

A Bit everyday A Byte every week

Setting up Laravel Development Environment with Docker

Introduction to building a basic Laravel development environment using Docker

Ray

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.

Recent Posts

Categories

Tags